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 * SPDX-License-Identifier: GPL-2.0-only
5 *
6 * Author: Stefano Avallone <stavallo@unina.it>
7 */
8
9#include "wifi-emlsr-test.h"
10
11#include "ns3/attribute-container.h"
12#include "ns3/boolean.h"
13#include "ns3/config.h"
14#include "ns3/ctrl-headers.h"
15#include "ns3/eht-configuration.h"
16#include "ns3/eht-frame-exchange-manager.h"
17#include "ns3/emlsr-manager.h"
18#include "ns3/log.h"
19#include "ns3/mgt-action-headers.h"
20#include "ns3/mobility-helper.h"
21#include "ns3/multi-model-spectrum-channel.h"
22#include "ns3/node-list.h"
23#include "ns3/packet-socket-helper.h"
24#include "ns3/packet-socket-server.h"
25#include "ns3/qos-txop.h"
26#include "ns3/rng-seed-manager.h"
27#include "ns3/rr-multi-user-scheduler.h"
28#include "ns3/simulator.h"
29#include "ns3/spectrum-wifi-helper.h"
30#include "ns3/spectrum-wifi-phy.h"
31#include "ns3/string.h"
32#include "ns3/wifi-net-device.h"
33
34#include <algorithm>
35#include <functional>
36#include <iomanip>
37
38using namespace ns3;
39
40NS_LOG_COMPONENT_DEFINE("WifiEmlsrTest");
41
44 "Check serialization and deserialization of the EML Operating Mode Notification frame")
45{
46}
47
48void
50{
51 MgtEmlOmn frame;
52
53 // Both EMLSR Mode and EMLMR Mode subfields set to 0 (no link bitmap);
55
56 frame.m_emlControl.emlsrMode = 1;
57 frame.SetLinkIdInBitmap(0);
58 frame.SetLinkIdInBitmap(5);
59 frame.SetLinkIdInBitmap(15);
60
61 // Adding Link Bitmap
63
64 NS_TEST_EXPECT_MSG_EQ((frame.GetLinkBitmap() == std::list<uint8_t>{0, 5, 15}),
65 true,
66 "Unexpected link bitmap");
67
68 auto padding = MicroSeconds(64);
69 auto transition = MicroSeconds(128);
70
74 frame.m_emlsrParamUpdate->transitionDelay =
76
77 // Adding the EMLSR Parameter Update field
79
82 padding,
83 "Unexpected EMLSR Padding Delay");
86 transition,
87 "Unexpected EMLSR Transition Delay");
88}
89
91 : TestCase(name)
92{
93}
94
95void
97 uint8_t phyId,
98 WifiConstPsduMap psduMap,
99 WifiTxVector txVector,
100 double txPowerW)
101{
102 auto linkId = mac->GetLinkForPhy(phyId);
103 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(), true, "No link found for PHY ID " << +phyId);
104 m_txPsdus.push_back({Simulator::Now(), psduMap, txVector, *linkId, phyId});
105
106 auto txDuration =
107 WifiPhy::CalculateTxDuration(psduMap, txVector, mac->GetWifiPhy(*linkId)->GetPhyBand());
108
109 for (const auto& [aid, psdu] : psduMap)
110 {
111 std::stringstream ss;
112 ss << std::setprecision(10) << "PSDU #" << m_txPsdus.size() << " Link ID "
113 << +linkId.value() << " Phy ID " << +phyId << " " << psdu->GetHeader(0).GetTypeString();
114 if (psdu->GetHeader(0).IsAction())
115 {
116 ss << " ";
117 WifiActionHeader actionHdr;
118 psdu->GetPayload(0)->PeekHeader(actionHdr);
119 actionHdr.Print(ss);
120 }
121 ss << " #MPDUs " << psdu->GetNMpdus() << " duration/ID " << psdu->GetHeader(0).GetDuration()
122 << " RA = " << psdu->GetAddr1() << " TA = " << psdu->GetAddr2()
123 << " ADDR3 = " << psdu->GetHeader(0).GetAddr3()
124 << " ToDS = " << psdu->GetHeader(0).IsToDs()
125 << " FromDS = " << psdu->GetHeader(0).IsFromDs();
126 if (psdu->GetHeader(0).IsQosData())
127 {
128 ss << " seqNo = {";
129 for (auto& mpdu : *PeekPointer(psdu))
130 {
131 ss << mpdu->GetHeader().GetSequenceNumber() << ",";
132 }
133 ss << "} TID = " << +psdu->GetHeader(0).GetQosTid();
134 }
135 NS_LOG_INFO(ss.str());
136
137 // if this frame is transmitted by an EMLSR client on an EMLSR links, in-device interference
138 // is configured and the TX duration exceeds the threshold (72us), MediumSyncDelay timer is
139 // (re)started at the end of the transmission
140 if (auto staMac = DynamicCast<StaWifiMac>(mac);
141 staMac && staMac->IsEmlsrLink(*linkId) &&
142 staMac->GetEmlsrManager()->GetMediumSyncDuration().IsStrictlyPositive())
143 {
144 const auto mustStartMsd =
145 staMac->GetEmlsrManager()->GetInDeviceInterference() &&
147
148 for (auto id : staMac->GetLinkIds())
149 {
150 // timer started on EMLSR links other than the link on which TX is starting,
151 // provided that a PHY is operating on the link and MediumSyncDuration is not null
152 if (!staMac->IsEmlsrLink(id) || id == *linkId || staMac->GetWifiPhy(id) == nullptr)
153 {
154 continue;
155 }
157 txDuration - TimeStep(1),
158 [=, hdrType = psdu->GetHeader(0).GetTypeString(), this]() {
159 // check if MSD timer was running on the link before completing transmission
160 bool msdWasRunning = staMac->GetEmlsrManager()
161 ->GetElapsedMediumSyncDelayTimer(id)
162 .has_value();
163 if (auto phy = staMac->GetWifiPhy(id);
164 !msdWasRunning && !mustStartMsd && phy && phy->IsStateSleep())
165 {
166 // if the MSD timer was not running before the end of the TX, it is not
167 // expected to be started and the PHY operating on this link is
168 // sleeping, do not check that the MSD timer is not started after the
169 // end of the TX, because it may be started because of the sleep period
170 // of the aux PHY
171 return;
172 }
173 Simulator::Schedule(TimeStep(2), [=, this]() {
175 id,
176 (msdWasRunning || mustStartMsd),
177 std::string("after transmitting ") + hdrType +
178 " on link " + std::to_string(*linkId));
179 });
180 });
181 }
182 }
183 }
184 NS_LOG_INFO("TX duration = " << txDuration.As(Time::MS) << " TXVECTOR = " << txVector << "\n");
185}
186
187void
189 uint8_t linkId,
190 bool isRunning,
191 const std::string& msg)
192{
193 auto time = staMac->GetEmlsrManager()->GetElapsedMediumSyncDelayTimer(linkId);
194 NS_TEST_ASSERT_MSG_EQ(time.has_value(),
195 isRunning,
197 << " Unexpected status for MediumSyncDelay timer on link " << +linkId
198 << " " << msg);
199 if (auto phy = staMac->GetWifiPhy(linkId))
200 {
201 auto currThreshold = phy->GetCcaEdThreshold();
202 NS_TEST_EXPECT_MSG_EQ((static_cast<int8_t>(currThreshold) ==
203 staMac->GetEmlsrManager()->GetMediumSyncOfdmEdThreshold()),
204 isRunning,
206 << " Unexpected value (" << currThreshold
207 << ") for CCA ED threshold on link " << +linkId << " " << msg);
208 }
209}
210
211void
213{
215 {
216 // if m_putAuxPhyToSleep is false, aux PHYs must not be put to sleep
217 sleep = false;
218 }
219
220 for (const auto& phy : staMac->GetDevice()->GetPhys())
221 {
222 if (phy->GetPhyId() == m_mainPhyId)
223 {
224 continue; // do not check the main PHY
225 }
226
227 auto linkId = staMac->GetLinkForPhy(phy);
228
229 if (linkId.has_value() && !staMac->IsEmlsrLink(*linkId))
230 {
231 continue; // this PHY is not operating on an EMLSR link
232 }
233
234 if (!sleep)
235 {
236 NS_TEST_EXPECT_MSG_EQ(phy->IsStateSleep(),
237 false,
239 << " PHY " << +phy->GetPhyId() << " is in unexpected state "
240 << phy->GetState()->GetState());
241 continue;
242 }
243
244 // if the PHY is in state TX or switching, sleep is postponed until their end
245 const auto delay =
246 (phy->IsStateTx() || phy->IsStateSwitching()) ? phy->GetDelayUntilIdle() : Time{0};
247
248 Simulator::Schedule(delay, [=, this]() {
249 NS_TEST_EXPECT_MSG_EQ(phy->IsStateSleep(),
250 true,
251 "PHY " << +phy->GetPhyId() << " is in unexpected state "
252 << phy->GetState()->GetState());
253 });
254 }
255}
256
257void
259 const EmlsrMainPhySwitchTrace& info)
260{
261 m_traceInfo[index] = info.Clone();
262}
263
264void
266 std::string_view reason,
267 const std::optional<uint8_t>& fromLinkId,
268 uint8_t toLinkId,
269 bool checkFromLinkId,
270 bool checkToLinkId)
271{
272 const auto traceInfoIt = m_traceInfo.find(index);
273 NS_TEST_ASSERT_MSG_EQ((traceInfoIt != m_traceInfo.cend()), true, "Expected stored trace info");
274 const auto& traceInfo = traceInfoIt->second;
275
276 NS_TEST_EXPECT_MSG_EQ(traceInfo->GetName(), reason, "Unexpected reason");
277
278 if (checkFromLinkId)
279 {
280 NS_TEST_ASSERT_MSG_EQ(traceInfo->fromLinkId.has_value(),
281 fromLinkId.has_value(),
282 "Unexpected stored from_link ID");
283 if (fromLinkId.has_value())
284 {
285 NS_TEST_EXPECT_MSG_EQ(+traceInfo->fromLinkId.value(),
286 +fromLinkId.value(),
287 "Unexpected from_link ID");
288 }
289 }
290
291 if (checkToLinkId)
292 {
293 NS_TEST_EXPECT_MSG_EQ(+traceInfo->toLinkId, +toLinkId, "Unexpected to_link ID");
294 }
295
296 m_traceInfo.erase(traceInfoIt);
297}
298
299void
301{
304 int64_t streamNumber = 100;
305
306 Config::SetDefault("ns3::WifiMac::MpduBufferSize", UintegerValue(64));
307 Config::SetDefault("ns3::EmlsrManager::InDeviceInterference", BooleanValue(true));
308 Config::SetDefault("ns3::EmlsrManager::PutAuxPhyToSleep", BooleanValue(m_putAuxPhyToSleep));
309
310 NodeContainer wifiApNode(1);
311 NodeContainer wifiStaNodes(m_nEmlsrStations);
312
313 WifiHelper wifi;
314 // wifi.EnableLogComponents ();
315 wifi.SetStandard(WIFI_STANDARD_80211be);
316 wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
317 "DataMode",
318 StringValue("EhtMcs0"),
319 "ControlMode",
320 StringValue("HtMcs0"));
321 wifi.ConfigEhtOptions("EmlsrActivated",
322 BooleanValue(true),
323 "TransitionTimeout",
325
326 // MLDs are configured with three links
327 SpectrumWifiPhyHelper phyHelper(3);
329 phyHelper.SetPcapCaptureType(WifiPhyHelper::PcapCaptureType::PCAP_PER_LINK);
330 phyHelper.Set(0, "ChannelSettings", StringValue("{2, 0, BAND_2_4GHZ, 0}"));
331 phyHelper.Set(1, "ChannelSettings", StringValue("{36, 0, BAND_5GHZ, 0}"));
332 phyHelper.Set(2, "ChannelSettings", StringValue("{1, 0, BAND_6GHZ, 0}"));
333 // Add three spectrum channels to use multi-RF interface
337
338 WifiMacHelper mac;
339 mac.SetType("ns3::ApWifiMac",
340 "Ssid",
341 SsidValue(Ssid("ns-3-ssid")),
342 "BeaconGeneration",
343 BooleanValue(true));
344 mac.SetApEmlsrManager("ns3::AdvancedApEmlsrManager",
345 "WaitTransDelayOnPsduRxError",
346 BooleanValue(true));
347
348 NetDeviceContainer apDevice = wifi.Install(phyHelper, mac, wifiApNode);
349
350 mac.SetType("ns3::StaWifiMac",
351 "Ssid",
352 SsidValue(Ssid("wrong-ssid")),
353 "MaxMissedBeacons",
354 UintegerValue(1e6), // do not deassociate
355 "ActiveProbing",
356 BooleanValue(false));
357 mac.SetEmlsrManager("ns3::AdvancedEmlsrManager",
358 "EmlsrLinkSet",
360 "MainPhyId",
362
363 NetDeviceContainer staDevices = wifi.Install(phyHelper, mac, wifiStaNodes);
364
366
367 for (uint32_t i = 0; i < staDevices.GetN(); i++)
368 {
369 auto device = DynamicCast<WifiNetDevice>(staDevices.Get(i));
370 auto staMac = DynamicCast<StaWifiMac>(device->GetMac());
371 auto emlsrManager = staMac->GetEmlsrManager();
372 NS_ASSERT_MSG(i < m_paddingDelay.size(), "Not enough padding delay values provided");
373 emlsrManager->SetAttribute("EmlsrPaddingDelay", TimeValue(m_paddingDelay.at(i)));
374 NS_ASSERT_MSG(i < m_transitionDelay.size(), "Not enough transition delay values provided");
375 emlsrManager->SetAttribute("EmlsrTransitionDelay", TimeValue(m_transitionDelay.at(i)));
376 emlsrManager->TraceConnectWithoutContext(
377 "MainPhySwitch",
379 }
380
381 if (m_nNonEmlsrStations > 0)
382 {
383 // create the other non-AP MLDs for which EMLSR is not activated
384 wifi.ConfigEhtOptions("EmlsrActivated", BooleanValue(false));
385 NodeContainer otherStaNodes(m_nNonEmlsrStations);
386 staDevices.Add(wifi.Install(phyHelper, mac, otherStaNodes));
387 wifiStaNodes.Add(otherStaNodes);
388 }
389
390 for (uint32_t i = 0; i < staDevices.GetN(); i++)
391 {
392 auto device = DynamicCast<WifiNetDevice>(staDevices.Get(i));
393 m_staMacs.push_back(DynamicCast<StaWifiMac>(device->GetMac()));
394 }
395
396 // Trace PSDUs passed to the PHY on AP MLD and non-AP MLDs
397 for (uint8_t phyId = 0; phyId < m_apMac->GetDevice()->GetNPhys(); phyId++)
398 {
400 "/NodeList/0/DeviceList/*/$ns3::WifiNetDevice/Phys/" + std::to_string(phyId) +
401 "/PhyTxPsduBegin",
403 }
404 for (std::size_t i = 0; i < m_nEmlsrStations + m_nNonEmlsrStations; i++)
405 {
406 for (uint8_t phyId = 0; phyId < m_staMacs[i]->GetDevice()->GetNPhys(); phyId++)
407 {
409 "/NodeList/" + std::to_string(i + 1) + "/DeviceList/*/$ns3::WifiNetDevice/Phys/" +
410 std::to_string(phyId) + "/PhyTxPsduBegin",
412 }
413 }
414
415 // Uncomment the lines below to write PCAP files
416 // phyHelper.EnablePcap("wifi-emlsr_AP", apDevice);
417 // phyHelper.EnablePcap("wifi-emlsr_STA", staDevices);
418
419 // Assign fixed streams to random variables in use
420 streamNumber += WifiHelper::AssignStreams(apDevice, streamNumber);
421 streamNumber += WifiHelper::AssignStreams(staDevices, streamNumber);
422
423 MobilityHelper mobility;
425
426 for (std::size_t id = 0; id <= m_nEmlsrStations + m_nNonEmlsrStations; id++)
427 {
428 // all non-AP MLDs are co-located
429 positionAlloc->Add(Vector(std::min<double>(id, 1), 0.0, 0.0));
430 }
431 mobility.SetPositionAllocator(positionAlloc);
432
433 mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
434 mobility.Install(wifiApNode);
435 mobility.Install(wifiStaNodes);
436
437 // install packet socket on all nodes
438 PacketSocketHelper packetSocket;
439 packetSocket.Install(wifiApNode);
440 packetSocket.Install(wifiStaNodes);
441
442 // install a packet socket server on all nodes
443 for (auto nodeIt = NodeList::Begin(); nodeIt != NodeList::End(); nodeIt++)
444 {
445 PacketSocketAddress srvAddr;
446 auto device = DynamicCast<WifiNetDevice>((*nodeIt)->GetDevice(0));
447 NS_TEST_ASSERT_MSG_NE(device, nullptr, "Expected a WifiNetDevice");
448 srvAddr.SetSingleDevice(device->GetIfIndex());
449 srvAddr.SetProtocol(1);
450
451 auto server = CreateObject<PacketSocketServer>();
452 server->SetLocal(srvAddr);
453 (*nodeIt)->AddApplication(server);
454 server->SetStartTime(Seconds(0)); // now
455 server->SetStopTime(m_duration);
456 }
457
458 // set DL and UL packet sockets
459 for (const auto& staMac : m_staMacs)
460 {
461 m_dlSockets.emplace_back();
462 m_dlSockets.back().SetSingleDevice(m_apMac->GetDevice()->GetIfIndex());
463 m_dlSockets.back().SetPhysicalAddress(staMac->GetDevice()->GetAddress());
464 m_dlSockets.back().SetProtocol(1);
465
466 m_ulSockets.emplace_back();
467 m_ulSockets.back().SetSingleDevice(staMac->GetDevice()->GetIfIndex());
468 m_ulSockets.back().SetPhysicalAddress(m_apMac->GetDevice()->GetAddress());
469 m_ulSockets.back().SetProtocol(1);
470 }
471
472 // schedule ML setup for one station at a time
473 m_apMac->TraceConnectWithoutContext("AssociatedSta",
475 Simulator::Schedule(Seconds(0), [&]() { m_staMacs[0]->SetSsid(Ssid("ns-3-ssid")); });
476}
477
480 std::size_t staId,
481 std::size_t count,
482 std::size_t pktSize) const
483{
484 auto client = CreateObject<PacketSocketClient>();
485 client->SetAttribute("PacketSize", UintegerValue(pktSize));
486 client->SetAttribute("MaxPackets", UintegerValue(count));
487 client->SetAttribute("Interval", TimeValue(MicroSeconds(0)));
488 client->SetRemote(dir == DOWNLINK ? m_dlSockets.at(staId) : m_ulSockets.at(staId));
489 client->SetStartTime(Seconds(0)); // now
490 client->SetStopTime(m_duration - Simulator::Now());
491
492 return client;
493}
494
495void
497{
498 if (m_lastAid == aid)
499 {
500 // another STA of this non-AP MLD has already fired this callback
501 return;
502 }
503 m_lastAid = aid;
504
505 // wait some time (5ms) to allow the completion of association
506 auto delay = MilliSeconds(5);
507
508 if (m_establishBaDl)
509 {
510 // trigger establishment of BA agreement with AP as originator
511 Simulator::Schedule(delay, [=, this]() {
512 m_apMac->GetDevice()->GetNode()->AddApplication(
513 GetApplication(DOWNLINK, aid - 1, 4, 1000));
514 });
515
516 delay += MilliSeconds(5);
517 }
518
519 if (m_establishBaUl)
520 {
521 // trigger establishment of BA agreement with AP as recipient
522 Simulator::Schedule(delay, [=, this]() {
523 m_staMacs[aid - 1]->GetDevice()->GetNode()->AddApplication(
524 GetApplication(UPLINK, aid - 1, 4, 1000));
525 });
526
527 delay += MilliSeconds(5);
528 }
529
530 Simulator::Schedule(delay, [=, this]() {
532 {
533 // make the next STA start ML discovery & setup
534 m_staMacs[aid]->SetSsid(Ssid("ns-3-ssid"));
535 return;
536 }
537 // all stations associated; start traffic if needed
538 StartTraffic();
539 // stop generation of beacon frames in order to avoid interference
540 m_apMac->SetAttribute("BeaconGeneration", BooleanValue(false));
541 });
542}
543
544void
546 Mac48Address dest,
547 uint8_t linkId,
549 bool blocked,
550 std::string description,
551 bool testUnblockedForOtherReasons)
552{
554 auto mask = mac->GetMacQueueScheduler()->GetQueueLinkMask(AC_BE, queueId, linkId);
555 NS_TEST_EXPECT_MSG_EQ(mask.has_value(),
556 true,
557 description << ": Expected to find a mask for EMLSR link " << +linkId);
558 if (blocked)
559 {
560 NS_TEST_EXPECT_MSG_EQ(mask->test(static_cast<std::size_t>(reason)),
561 true,
562 description << ": Expected EMLSR link " << +linkId
563 << " to be blocked for reason " << reason);
564 if (testUnblockedForOtherReasons)
565 {
566 NS_TEST_EXPECT_MSG_EQ(mask->count(),
567 1,
568 description << ": Expected EMLSR link " << +linkId
569 << " to be blocked for one reason only");
570 }
571 }
572 else if (testUnblockedForOtherReasons)
573 {
574 NS_TEST_EXPECT_MSG_EQ(mask->none(),
575 true,
576 description << ": Expected EMLSR link " << +linkId
577 << " to be unblocked");
578 }
579 else
580 {
581 NS_TEST_EXPECT_MSG_EQ(mask->test(static_cast<std::size_t>(reason)),
582 false,
583 description << ": Expected EMLSR link " << +linkId
584 << " to be unblocked for reason " << reason);
585 }
586}
587
588EmlOmnExchangeTest::EmlOmnExchangeTest(const std::set<uint8_t>& linksToEnableEmlsrOn,
589 Time transitionTimeout)
590 : EmlsrOperationsTestBase("Check EML Notification exchange"),
591 m_checkEmlsrLinksCount(0),
592 m_emlNotificationDroppedCount(0)
593{
594 m_linksToEnableEmlsrOn = linksToEnableEmlsrOn;
597 m_transitionTimeout = transitionTimeout;
598 m_duration = Seconds(0.5);
599}
600
601void
603{
605
607 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
608 {
609 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
610 }
611
612 m_staMacs[0]->TraceConnectWithoutContext("AckedMpdu",
614 m_staMacs[0]->TraceConnectWithoutContext("DroppedMpdu",
616}
617
618void
620 uint8_t phyId,
621 WifiConstPsduMap psduMap,
622 WifiTxVector txVector,
623 double txPowerW)
624{
625 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
626 auto linkId = m_txPsdus.back().linkId;
627
628 auto psdu = psduMap.begin()->second;
629
630 switch (psdu->GetHeader(0).GetType())
631 {
633 NS_TEST_EXPECT_MSG_EQ(+linkId, +m_mainPhyId, "AssocReq not sent by the main PHY");
634 CheckEmlCapabilitiesInAssocReq(*psdu->begin(), txVector, linkId);
635 break;
636
638 CheckEmlCapabilitiesInAssocResp(*psdu->begin(), txVector, linkId);
639 break;
640
642 if (auto [category, action] = WifiActionHeader::Peek(psdu->GetPayload(0));
644 action.protectedEhtAction ==
646 {
647 CheckEmlNotification(psdu, txVector, linkId);
648
650 m_staMacs[0]->GetLinkIdByAddress(psdu->GetAddr2()) == linkId)
651 {
652 // transmitted by non-AP MLD, we need to corrupt it
653 m_uidList.push_front(psdu->GetPacket()->GetUid());
655 }
656 break;
657 }
658
659 default:;
660 }
661}
662
663void
665 const WifiTxVector& txVector,
666 uint8_t linkId)
667{
669 mpdu->GetPacket()->PeekHeader(frame);
670
671 const auto& mle = frame.Get<MultiLinkElement>();
672 NS_TEST_ASSERT_MSG_EQ(mle.has_value(), true, "Multi-Link Element must be present in AssocReq");
673
674 NS_TEST_ASSERT_MSG_EQ(mle->HasEmlCapabilities(),
675 true,
676 "Multi-Link Element in AssocReq must have EML Capabilities");
677 NS_TEST_ASSERT_MSG_EQ(mle->IsEmlsrSupported(),
678 true,
679 "EML Support subfield of EML Capabilities in AssocReq must be set to 1");
680 NS_TEST_ASSERT_MSG_EQ(mle->GetEmlsrPaddingDelay(),
681 m_paddingDelay.at(0),
682 "Unexpected Padding Delay in EML Capabilities included in AssocReq");
683 NS_TEST_ASSERT_MSG_EQ(mle->GetEmlsrTransitionDelay(),
684 m_transitionDelay.at(0),
685 "Unexpected Transition Delay in EML Capabilities included in AssocReq");
686}
687
688void
690 const WifiTxVector& txVector,
691 uint8_t linkId)
692{
693 bool sentToEmlsrClient =
694 (m_staMacs[0]->GetLinkIdByAddress(mpdu->GetHeader().GetAddr1()) == linkId);
695
696 if (!sentToEmlsrClient)
697 {
698 // nothing to check
699 return;
700 }
701
703 mpdu->GetPacket()->PeekHeader(frame);
704
705 const auto& mle = frame.Get<MultiLinkElement>();
706 NS_TEST_ASSERT_MSG_EQ(mle.has_value(), true, "Multi-Link Element must be present in AssocResp");
707
708 NS_TEST_ASSERT_MSG_EQ(mle->HasEmlCapabilities(),
709 true,
710 "Multi-Link Element in AssocResp must have EML Capabilities");
711 NS_TEST_ASSERT_MSG_EQ(mle->IsEmlsrSupported(),
712 true,
713 "EML Support subfield of EML Capabilities in AssocResp must be set to 1");
715 mle->GetTransitionTimeout(),
717 "Unexpected Transition Timeout in EML Capabilities included in AssocResp");
718}
719
720void
722 const WifiTxVector& txVector,
723 uint8_t linkId)
724{
725 MgtEmlOmn frame;
726 auto mpdu = *psdu->begin();
727 auto pkt = mpdu->GetPacket()->Copy();
729 pkt->RemoveHeader(frame);
730 NS_LOG_DEBUG(frame);
731
732 bool sentbyNonApMld = m_staMacs[0]->GetLinkIdByAddress(mpdu->GetHeader().GetAddr2()) == linkId;
733
735 1,
736 "EMLSR Mode subfield should be set to 1 (frame sent by non-AP MLD: "
737 << std::boolalpha << sentbyNonApMld << ")");
738
740 0,
741 "EMLMR Mode subfield should be set to 0 (frame sent by non-AP MLD: "
742 << std::boolalpha << sentbyNonApMld << ")");
743
745 true,
746 "Link Bitmap subfield should be present (frame sent by non-AP MLD: "
747 << std::boolalpha << sentbyNonApMld << ")");
748
749 auto setupLinks = m_staMacs[0]->GetSetupLinkIds();
750 std::list<uint8_t> expectedEmlsrLinks;
751 std::set_intersection(setupLinks.begin(),
752 setupLinks.end(),
755 std::back_inserter(expectedEmlsrLinks));
756
757 NS_TEST_EXPECT_MSG_EQ((expectedEmlsrLinks == frame.GetLinkBitmap()),
758 true,
759 "Unexpected Link Bitmap subfield (frame sent by non-AP MLD: "
760 << std::boolalpha << sentbyNonApMld << ")");
761
762 if (!sentbyNonApMld)
763 {
764 // the frame has been sent by the AP MLD
767 0,
768 "EMLSR Parameter Update Control should be set to 0 in frames sent by the AP MLD");
769
770 // as soon as the non-AP MLD receives this frame, it sets the EMLSR links
771 auto delay = WifiPhy::CalculateTxDuration(psdu,
772 txVector,
773 m_staMacs[0]->GetWifiPhy(linkId)->GetPhyBand()) +
774 MicroSeconds(1); // to account for propagation delay
776 }
777
779 +linkId,
780 "EML Notification received on unexpected link (frame sent by non-AP MLD: "
781 << std::boolalpha << sentbyNonApMld << ")");
782}
783
784void
786{
787 const auto& hdr = mpdu->GetHeader();
788
789 if (hdr.IsMgt() && hdr.IsAction())
790 {
791 if (auto [category, action] = WifiActionHeader::Peek(mpdu->GetPacket());
793 action.protectedEhtAction ==
795 {
796 // the EML Operating Mode Notification frame that the non-AP MLD sent has been
797 // acknowledged; after the transition timeout, the EMLSR links have been set
800 this);
801 }
802 }
803}
804
805void
807{
808 const auto& hdr = mpdu->GetHeader();
809
810 if (hdr.IsMgt() && hdr.IsAction())
811 {
812 if (auto [category, action] = WifiActionHeader::Peek(mpdu->GetPacket());
814 action.protectedEhtAction ==
816 {
817 // the EML Operating Mode Notification frame has been dropped. Don't
818 // corrupt it anymore
820 }
821 }
822}
823
824void
826{
828
829 auto setupLinks = m_staMacs[0]->GetSetupLinkIds();
830 std::set<uint8_t> expectedEmlsrLinks;
831 std::set_intersection(setupLinks.begin(),
832 setupLinks.end(),
835 std::inserter(expectedEmlsrLinks, expectedEmlsrLinks.end()));
836
837 NS_TEST_EXPECT_MSG_EQ((expectedEmlsrLinks == m_staMacs[0]->GetEmlsrManager()->GetEmlsrLinks()),
838 true,
839 "Unexpected set of EMLSR links)");
840}
841
842void
844{
847
849 2,
850 "Unexpected number of times CheckEmlsrLinks() is called");
853 1,
854 "Unexpected number of times the EML Notification frame is dropped due to max retry limit");
855
857}
858
860 : EmlsrOperationsTestBase("Check EML DL TXOP transmissions (" +
861 std::to_string(params.nEmlsrStations) + "," +
862 std::to_string(params.nNonEmlsrStations) + ")"),
863 m_emlsrLinks(params.linksToEnableEmlsrOn),
864 m_emlsrEnabledTime(0),
865 m_fe2to3delay(MilliSeconds(20)),
866 m_countQoSframes(0),
867 m_countBlockAck(0)
868{
869 m_nEmlsrStations = params.nEmlsrStations;
870 m_nNonEmlsrStations = params.nNonEmlsrStations;
871 m_linksToEnableEmlsrOn = {}; // do not enable EMLSR right after association
872 m_mainPhyId = 1;
873 m_paddingDelay = params.paddingDelay;
874 m_transitionDelay = params.transitionDelay;
875 m_transitionTimeout = params.transitionTimeout;
876 m_establishBaDl = true;
877 m_putAuxPhyToSleep = params.putAuxPhyToSleep;
878 m_duration = Seconds(1.5);
879
880 NS_ABORT_MSG_IF(params.linksToEnableEmlsrOn.size() < 2,
881 "This test requires at least two links to be configured as EMLSR links");
882}
883
884void
886 uint8_t phyId,
887 WifiConstPsduMap psduMap,
888 WifiTxVector txVector,
889 double txPowerW)
890{
891 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
892 auto linkId = m_txPsdus.back().linkId;
893
894 auto psdu = psduMap.begin()->second;
895 auto nodeId = mac->GetDevice()->GetNode()->GetId();
896
897 switch (psdu->GetHeader(0).GetType())
898 {
900 NS_ASSERT_MSG(nodeId > 0, "APs do not send AssocReq frames");
901 if (nodeId <= m_nEmlsrStations)
902 {
903 NS_TEST_EXPECT_MSG_EQ(+linkId, +m_mainPhyId, "AssocReq not sent by the main PHY");
904 // this AssocReq is being sent by an EMLSR client. The other EMLSR links should be
905 // in powersave mode after association; we let the non-EMLSR links transition to
906 // active mode (by sending data null frames) after association
907 for (const auto id : m_staMacs.at(nodeId - 1)->GetLinkIds())
908 {
909 if (id != linkId && m_emlsrLinks.count(id) == 1)
910 {
911 m_staMacs[nodeId - 1]->SetPowerSaveMode({true, id});
912 }
913 }
914 }
915 break;
916
917 case WIFI_MAC_MGT_ACTION: {
918 auto [category, action] = WifiActionHeader::Peek(psdu->GetPayload(0));
919
920 if ((category == WifiActionHeader::PROTECTED_EHT) &&
921 (action.protectedEhtAction ==
923 {
924 nodeId == 0 ? CheckApEmlNotificationFrame(*psdu->begin(), txVector, linkId)
925 : CheckStaEmlNotificationFrame(*psdu->begin(), txVector, linkId);
926 }
927 else if (category == WifiActionHeader::BLOCK_ACK &&
929 {
930 CheckPmModeAfterAssociation(psdu->GetAddr1());
931 }
932 }
933 break;
934
936 CheckInitialControlFrame(*psdu->begin(), txVector, linkId);
937 break;
938
939 case WIFI_MAC_QOSDATA:
940 CheckQosFrames(psduMap, txVector, linkId);
941 break;
942
944 CheckBlockAck(psduMap, txVector, phyId);
945 break;
946
947 case WIFI_MAC_CTL_END:
948 if (auto apMac = DynamicCast<ApWifiMac>(mac))
949 {
950 const auto txDuration =
952 txVector,
953 apMac->GetDevice()->GetPhy(phyId)->GetPhyBand());
954 for (std::size_t i = 0; i < m_nEmlsrStations; ++i)
955 {
956 if (m_staMacs[i]->IsEmlsrLink(linkId) &&
957 m_staMacs[i]->GetWifiPhy(linkId) ==
958 m_staMacs[i]->GetDevice()->GetPhy(m_mainPhyId))
959 {
960 // AP is terminating a TXOP on an EMLSR link on which the main PHY is operating,
961 // aux PHYs should resume from sleep
962 Simulator::Schedule(txDuration + TimeStep(1),
964 this,
965 m_staMacs[i],
966 false);
967 }
968 }
969 }
970 break;
971
972 default:;
973 }
974}
975
976void
978{
979 // Channel switch delay should be less than the ICF padding duration, otherwise
980 // DL TXOPs cannot be initiated on auxiliary links
981 auto delay = std::min(MicroSeconds(100),
982 *std::min_element(m_paddingDelay.cbegin(), m_paddingDelay.cend()));
983 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(75)));
984
986
988 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
989 {
990 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
991 }
992
993 m_apMac->GetQosTxop(AC_BE)->SetTxopLimits(
994 {MicroSeconds(3200), MicroSeconds(3200), MicroSeconds(3200)});
995
997 {
998 auto muScheduler =
1000 m_apMac->AggregateObject(muScheduler);
1001 for (uint8_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
1002 {
1003 m_apMac->GetFrameExchangeManager(linkId)->GetAckManager()->SetAttribute(
1004 "DlMuAckSequenceType",
1006 }
1007 }
1008}
1009
1010void
1012{
1014 {
1015 // we are done with association and Block Ack agreement; we can now enable EMLSR mode
1016 m_lastAid = 0;
1018 return;
1019 }
1020
1021 // we are done with sending EML Operating Mode Notification frames. We can now generate
1022 // packets for all non-AP MLDs
1023 for (std::size_t i = 0; i < m_nEmlsrStations + m_nNonEmlsrStations; i++)
1024 {
1025 // when multiple non-AP MLDs are present, MU transmission are used. Given that the
1026 // available bandwidth decreases as the number of non-AP MLDs increases, compute the
1027 // number of packets to generate so that we always have two A-MPDUs per non-AP MLD
1028 std::size_t count = 8 / (m_nEmlsrStations + m_nNonEmlsrStations);
1029 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, i, count, 450));
1030 }
1031
1032 // in case of 2 EMLSR clients using no non-EMLSR link, generate one additional short
1033 // packet to each EMLSR client to test transition delay
1034 if (m_nEmlsrStations == 2 && m_apMac->GetNLinks() == m_emlsrLinks.size())
1035 {
1037 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 0, 1, 40));
1038 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 1, 1, 40));
1039 });
1040 }
1041
1042 // schedule the transmission of EML Operating Mode Notification frames to disable EMLSR mode
1043 // and the generation of other packets destined to the EMLSR clients
1044 for (std::size_t id = 0; id < m_nEmlsrStations; id++)
1045 {
1046 Simulator::Schedule(m_fe2to3delay + MilliSeconds(5 * (id + 1)), [=, this]() {
1047 m_staMacs.at(id)->GetEmlsrManager()->SetAttribute(
1048 "EmlsrLinkSet",
1050 });
1051
1053 m_apMac->GetDevice()->GetNode()->AddApplication(
1055 });
1056 }
1057}
1058
1059void
1061{
1062 m_staMacs.at(m_lastAid)->GetEmlsrManager()->SetAttribute(
1063 "EmlsrLinkSet",
1065 m_lastAid++;
1066 Simulator::Schedule(MilliSeconds(5), [=, this]() {
1068 {
1069 // make the next STA send EML Notification frame
1071 return;
1072 }
1073 // all stations enabled EMLSR mode; start traffic
1075 StartTraffic();
1076 });
1077}
1078
1079void
1081{
1082 auto psduIt = m_txPsdus.cbegin();
1083
1084 // lambda to jump to the next QoS data frame or MU-RTS Trigger Frame transmitted
1085 // to an EMLSR client
1086 auto jumpToQosDataOrMuRts = [&]() {
1087 while (psduIt != m_txPsdus.cend() &&
1088 !psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData())
1089 {
1090 auto psdu = psduIt->psduMap.cbegin()->second;
1091 if (psdu->GetHeader(0).IsTrigger())
1092 {
1093 CtrlTriggerHeader trigger;
1094 psdu->GetPayload(0)->PeekHeader(trigger);
1095 if (trigger.IsMuRts())
1096 {
1097 break;
1098 }
1099 }
1100 psduIt++;
1101 }
1102 };
1103
1104 /**
1105 * Before enabling EMLSR mode, no MU-RTS TF should be sent. Four packets are generated
1106 * after association to trigger the establishment of a Block Ack agreement. The TXOP Limit
1107 * and the MCS are set such that two packets can be transmitted in a TXOP, hence we expect
1108 * that the AP MLD sends two A-MPDUs to each non-AP MLD.
1109 *
1110 * EMLSR client with EMLSR mode to be enabled on all links: after ML setup, all other links
1111 * stay in power save mode, hence BA establishment occurs on the same link.
1112 *
1113 * [link 0]
1114 * ───────────────────────────────────────────────────────────────────────────
1115 * | power save mode
1116 *
1117 * ┌─────┐ ┌─────┐ ┌───┬───┐ ┌───┬───┐
1118 * ┌───┐ │Assoc│ │ADDBA│ ┌───┐ │QoS│QoS│ │QoS│QoS│
1119 * [link 1] │ACK│ │Resp │ │ Req │ │ACK│ │ 0 │ 1 │ │ 2 │ 3 │
1120 * ───┬─────┬┴───┴──┴─────┴┬───┬─┴─────┴┬───┬─┬─────┬┴───┴─┴───┴───┴┬──┬─┴───┴───┴┬──┬───
1121 * │Assoc│ │ACK│ │ACK│ │ADDBA│ │BA│ │BA│
1122 * │ Req │ └───┘ └───┘ │Resp │ └──┘ └──┘
1123 * └─────┘ └─────┘
1124 *
1125 * [link 2]
1126 * ───────────────────────────────────────────────────────────────────────────
1127 * | power save mode
1128 *
1129 *
1130 * EMLSR client with EMLSR mode to be enabled on not all the links: after ML setup,
1131 * the other EMLSR links stay in power save mode, the non-EMLSR link (link 1) transitions
1132 * to active mode.
1133 *
1134 * ┌─────┐ ┌───┬───┐
1135 * ┌───┐ │ADDBA│ ┌───┐ │QoS│QoS│
1136 * [link 0 - non EMLSR] │ACK│ │ Req │ │ACK│ │ 2 │ 3 │
1137 * ──────────────────────────────┬────┬┴───┴──┴─────┴┬───┬─┬─────┬┴───┴─┴───┴───┴┬──┬─
1138 * │Data│ │ACK│ │ADDBA│ │BA│
1139 * │Null│ └───┘ │Resp │ └──┘
1140 * └────┘ └─────┘
1141 * ┌─────┐ ┌───┬───┐
1142 * ┌───┐ │Assoc│ │QoS│QoS│
1143 * [link 1] │ACK│ │Resp │ │ 0 │ 1 │
1144 * ───┬─────┬┴───┴──┴─────┴┬───┬──────────────────────────────────┴───┴───┴┬──┬───────
1145 * │Assoc│ │ACK│ │BA│
1146 * │ Req │ └───┘ └──┘
1147 * └─────┘
1148 *
1149 * [link 2]
1150 * ───────────────────────────────────────────────────────────────────────────
1151 * | power save mode
1152 *
1153 * Non-EMLSR client (not shown): after ML setup, all other links transition to active mode
1154 * by sending a Data Null frame; QoS data frame exchanges occur on two links simultaneously.
1155 */
1156 for (std::size_t i = 0; i < m_nEmlsrStations + m_nNonEmlsrStations; i++)
1157 {
1158 std::set<uint8_t> linkIds;
1159
1160 jumpToQosDataOrMuRts();
1161 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend() &&
1162 psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData()),
1163 true,
1164 "Expected at least one QoS data frame before enabling EMLSR mode");
1165 linkIds.insert(psduIt->linkId);
1166 const auto firstAmpduTxEnd =
1167 psduIt->startTx +
1168 WifiPhy::CalculateTxDuration(psduIt->psduMap,
1169 psduIt->txVector,
1170 m_staMacs[i]->GetWifiPhy(psduIt->linkId)->GetPhyBand());
1171 psduIt++;
1172
1173 jumpToQosDataOrMuRts();
1174 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend() &&
1175 psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData()),
1176 true,
1177 "Expected at least two QoS data frames before enabling EMLSR mode");
1178 linkIds.insert(psduIt->linkId);
1179 const auto secondAmpduTxStart = psduIt->startTx;
1180 psduIt++;
1181
1182 /**
1183 * If this is an EMLSR client and there is no setup link other than the one used to
1184 * establish association that is not an EMLSR link, then the two A-MPDUs are sent one
1185 * after another on the link used to establish association.
1186 */
1187 auto setupLinks = m_staMacs[i]->GetSetupLinkIds();
1188 if (i < m_nEmlsrStations &&
1189 std::none_of(setupLinks.begin(), setupLinks.end(), [&](auto&& linkId) {
1190 return linkId != m_mainPhyId && m_emlsrLinks.count(linkId) == 0;
1191 }))
1192 {
1193 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
1194 1,
1195 "Expected both A-MPDUs to be sent on the same link");
1196 NS_TEST_EXPECT_MSG_EQ(*linkIds.begin(), +m_mainPhyId, "A-MPDUs sent on incorrect link");
1197 NS_TEST_EXPECT_MSG_LT(firstAmpduTxEnd,
1198 secondAmpduTxStart,
1199 "A-MPDUs are not sent one after another");
1200 }
1201 /**
1202 * Otherwise, the two A-MPDUs can be sent concurrently on two distinct links (may be
1203 * the link used to establish association and a non-EMLSR link).
1204 */
1205 else
1206 {
1207 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
1208 2,
1209 "Expected A-MPDUs to be sent on distinct links");
1210 NS_TEST_EXPECT_MSG_GT(firstAmpduTxEnd,
1211 secondAmpduTxStart,
1212 "A-MPDUs are not sent concurrently");
1213 }
1214 }
1215
1216 /**
1217 * After enabling EMLSR mode, MU-RTS TF should only be sent on EMLSR links. After the exchange
1218 * of EML Operating Mode Notification frames, a number of packets are generated at the AP MLD
1219 * to prepare two A-MPDUs for each non-AP MLD.
1220 *
1221 * EMLSR client with EMLSR mode to be enabled on all links (A is the EMLSR client, B is the
1222 * non-EMLSR client):
1223 * ┌─────┬─────┐
1224 * │QoS 4│QoS 5│
1225 * │ to A│ to A│
1226 * ┌───┐ ├─────┼─────┤
1227 * │MU │ │QoS 4│QoS 5│
1228 * [link 0] │RTS│ │ to B│ to B│
1229 * ──────────────────────────┴───┴┬───┬┴─────┴─────┴┬──┬────────────
1230 * │CTS│ │BA│
1231 * ├───┤ ├──┤
1232 * │CTS│ │BA│
1233 * └───┘ └──┘
1234 * ┌───┐ ┌─────┬─────┐
1235 * ┌───┐ │EML│ │QoS 6│QoS 7│
1236 * [link 1] │ACK│ │OM │ │ to B│ to B│
1237 * ────┬───┬┴───┴──┴───┴┬───┬─┴─────┴─────┴┬──┬────────────────────────────────────
1238 * │EML│ │ACK│ │BA│
1239 * │OM │ └───┘ └──┘
1240 * └───┘
1241 * ┌───┐ ┌─────┬─────┐
1242 * │MU │ │QoS 6│QoS 7│
1243 * [link 2] │RTS│ │ to A│ to A│
1244 * ─────────────────────────────────────────────────────────┴───┴┬───┬┴─────┴─────┴┬──┬─
1245 * │CTS│ │BA│
1246 * └───┘ └──┘
1247 *
1248 * EMLSR client with EMLSR mode to be enabled on not all the links (A is the EMLSR client,
1249 * B is the non-EMLSR client):
1250 * ┌─────┬─────┐
1251 * │QoS 4│QoS 5│
1252 * │ to A│ to A│
1253 * ├─────┼─────┤
1254 * │QoS 4│QoS 5│
1255 * [link 0 - non EMLSR] │ to B│ to B│
1256 * ───────────────────────────┴─────┴─────┴┬──┬───────────────────────────
1257 * │BA│
1258 * ├──┤
1259 * │BA│
1260 * └──┘
1261 * ┌─────┬─────┐
1262 * │QoS 6│QoS 7│
1263 * │ to A│ to A│
1264 * ┌───┐ ┌───┐ ├─────┼─────┤
1265 * ┌───┐ │EML│ │MU │ │QoS 6│QoS 7│
1266 * [link 1] │ACK│ │OM │ │RTS│ │ to B│ to B│
1267 * ────┬───┬┴───┴──┴───┴┬───┬─┴───┴┬───┬┴─────┴─────┴┬──┬────────────
1268 * │EML│ │ACK│ │CTS│ │BA│
1269 * │OM │ └───┘ ├───┤ ├──┤
1270 * └───┘ │CTS│ │BA│
1271 * └───┘ └──┘
1272 *
1273 * [link 2]
1274 * ────────────────────────────────────────────────────────────────────────────────
1275 */
1276
1277 /// Store a QoS data frame or an MU-RTS TF followed by a QoS data frame
1278 using FrameExchange = std::list<decltype(psduIt)>;
1279
1280 std::vector<std::list<FrameExchange>> frameExchanges(m_nEmlsrStations);
1281
1282 // compute all frame exchanges involving EMLSR clients
1283 while (psduIt != m_txPsdus.cend())
1284 {
1285 jumpToQosDataOrMuRts();
1286 if (psduIt == m_txPsdus.cend())
1287 {
1288 break;
1289 }
1290
1291 if (IsTrigger(psduIt->psduMap))
1292 {
1293 CtrlTriggerHeader trigger;
1294 psduIt->psduMap.cbegin()->second->GetPayload(0)->PeekHeader(trigger);
1295 // this is an MU-RTS TF starting a new frame exchange sequence; add it to all
1296 // the addressed EMLSR clients
1298 true,
1299 "jumpToQosDataOrMuRts does not return TFs other than MU-RTS");
1300 for (const auto& userInfo : trigger)
1301 {
1302 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
1303 {
1304 if (m_staMacs.at(i)->GetAssociationId() == userInfo.GetAid12())
1305 {
1306 frameExchanges.at(i).emplace_back(FrameExchange{psduIt});
1307 break;
1308 }
1309 }
1310 }
1311 psduIt++;
1312 continue;
1313 }
1314
1315 // we get here if psduIt points to a psduMap containing QoS data frame(s); find (if any)
1316 // the QoS data frame(s) addressed to EMLSR clients and add them to the appropriate
1317 // frame exchange sequence
1318 for (const auto& staIdPsduPair : psduIt->psduMap)
1319 {
1320 std::for_each_n(m_staMacs.cbegin(), m_nEmlsrStations, [&](auto&& staMac) {
1321 if (!staMac->GetLinkIdByAddress(staIdPsduPair.second->GetAddr1()))
1322 {
1323 // not addressed to this non-AP MLD
1324 return;
1325 }
1326 // a QoS data frame starts a new frame exchange sequence if there is no previous
1327 // MU-RTS TF that has been sent on the same link and is not already followed by
1328 // a QoS data frame
1329 std::size_t id = staMac->GetDevice()->GetNode()->GetId() - 1;
1330 for (auto& frameExchange : frameExchanges.at(id))
1331 {
1332 if (IsTrigger(frameExchange.front()->psduMap) &&
1333 frameExchange.front()->linkId == psduIt->linkId &&
1334 frameExchange.size() == 1)
1335 {
1336 auto it = std::next(frameExchange.front());
1337 while (it != m_txPsdus.end())
1338 {
1339 // stop at the first frame other than CTS sent on this link
1340 if (it->linkId == psduIt->linkId &&
1341 !it->psduMap.begin()->second->GetHeader(0).IsCts())
1342 {
1343 break;
1344 }
1345 ++it;
1346 }
1347 if (it == psduIt)
1348 {
1349 // the QoS data frame actually followed the MU-RTS TF
1350 frameExchange.emplace_back(psduIt);
1351 return;
1352 }
1353 }
1354 }
1355 frameExchanges.at(id).emplace_back(FrameExchange{psduIt});
1356 });
1357 }
1358 psduIt++;
1359 }
1360
1361 /**
1362 * Let's focus on the first two frame exchanges for each EMLSR clients. If all setup links are
1363 * EMLSR links, both frame exchanges are protected by MU-RTS TF and occur one after another.
1364 * Otherwise, one frame exchange occurs on the non-EMLSR link and is not protected by
1365 * MU-RTS TF; the other frame exchange occurs on an EMLSR link and is protected by MU-RTS TF.
1366 */
1367 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
1368 {
1369 NS_TEST_EXPECT_MSG_GT_OR_EQ(frameExchanges.at(i).size(),
1370 2,
1371 "Expected at least 2 frame exchange sequences "
1372 << "involving EMLSR client " << i);
1373
1374 auto firstExchangeIt = frameExchanges.at(i).begin();
1375 auto secondExchangeIt = std::next(firstExchangeIt);
1376
1377 const auto firstAmpduTxEnd =
1378 firstExchangeIt->back()->startTx +
1380 firstExchangeIt->back()->psduMap,
1381 firstExchangeIt->back()->txVector,
1382 m_staMacs[i]->GetWifiPhy(firstExchangeIt->back()->linkId)->GetPhyBand());
1383 const auto secondAmpduTxStart = secondExchangeIt->front()->startTx;
1384
1385 if (m_staMacs[i]->GetNLinks() == m_emlsrLinks.size())
1386 {
1387 // all links are EMLSR links
1388 NS_TEST_EXPECT_MSG_EQ(IsTrigger(firstExchangeIt->front()->psduMap),
1389 true,
1390 "Expected an MU-RTS TF as ICF of first frame exchange sequence");
1392 firstExchangeIt->back()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1393 true,
1394 "Expected a QoS data frame in the first frame exchange sequence");
1395
1396 NS_TEST_EXPECT_MSG_EQ(IsTrigger(secondExchangeIt->front()->psduMap),
1397 true,
1398 "Expected an MU-RTS TF as ICF of second frame exchange sequence");
1400 secondExchangeIt->back()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1401 true,
1402 "Expected a QoS data frame in the second frame exchange sequence");
1403
1404 NS_TEST_EXPECT_MSG_LT(firstAmpduTxEnd,
1405 secondAmpduTxStart,
1406 "A-MPDUs are not sent one after another");
1407 }
1408 else
1409 {
1410 std::vector<uint8_t> nonEmlsrIds;
1411 auto setupLinks = m_staMacs[i]->GetSetupLinkIds();
1412 std::set_difference(setupLinks.begin(),
1413 setupLinks.end(),
1414 m_emlsrLinks.begin(),
1415 m_emlsrLinks.end(),
1416 std::back_inserter(nonEmlsrIds));
1417 NS_TEST_ASSERT_MSG_EQ(nonEmlsrIds.size(), 1, "Unexpected number of non-EMLSR links");
1418
1419 auto nonEmlsrLinkExchangeIt = firstExchangeIt->front()->linkId == nonEmlsrIds[0]
1420 ? firstExchangeIt
1421 : secondExchangeIt;
1422 NS_TEST_EXPECT_MSG_EQ(IsTrigger(nonEmlsrLinkExchangeIt->front()->psduMap),
1423 false,
1424 "Did not expect an MU-RTS TF as ICF on non-EMLSR link");
1426 nonEmlsrLinkExchangeIt->front()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1427 true,
1428 "Expected a QoS data frame on the non-EMLSR link");
1429
1430 auto emlsrLinkExchangeIt =
1431 nonEmlsrLinkExchangeIt == firstExchangeIt ? secondExchangeIt : firstExchangeIt;
1432 NS_TEST_EXPECT_MSG_NE(+emlsrLinkExchangeIt->front()->linkId,
1433 +nonEmlsrIds[0],
1434 "Expected this exchange not to occur on non-EMLSR link");
1435 NS_TEST_EXPECT_MSG_EQ(IsTrigger(emlsrLinkExchangeIt->front()->psduMap),
1436 true,
1437 "Expected an MU-RTS TF as ICF on the EMLSR link");
1439 emlsrLinkExchangeIt->back()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1440 true,
1441 "Expected a QoS data frame on the EMLSR link");
1442
1443 NS_TEST_EXPECT_MSG_GT(firstAmpduTxEnd,
1444 secondAmpduTxStart,
1445 "A-MPDUs are not sent concurrently");
1446 }
1447
1448 // we are done with processing the first two frame exchanges, remove them
1449 frameExchanges.at(i).erase(firstExchangeIt);
1450 frameExchanges.at(i).erase(secondExchangeIt);
1451 }
1452
1453 /**
1454 * A and B are two EMLSR clients. No ICF before the second QoS data frame because B
1455 * has not switched to listening mode. ICF is sent before the third QoS data frame because
1456 * A has switched to listening mode. C is a non-EMLSR client.
1457 *
1458 * ┌─────┐ A switches to listening
1459 * │QoS x│ after transition delay
1460 * │ to A│ |
1461 * ┌───┐ ├─────┤ ┌─────┐
1462 * │MU │ │QoS x│ │QoS y│
1463 * [link 0] │RTS│ │ to B│ │ to B│
1464 * ────────────┴───┴┬───┬┴─────┴┬──┬┴─────┴┬──┬────────────
1465 * │CTS│ │BA│ │BA│
1466 * ├───┤ ├──┤ └──┘
1467 * │CTS│ │BA│
1468 * └───┘ └──┘ AP continues the TXOP A switches to listening
1469 * after PIFS recovery after transition delay
1470 * │ │
1471 * ┌─────┐ ┌───┐ ┌─────┐ │┌───┐ ┌───┐
1472 * │QoS z│ │MU │ │QoS x│ ││MU │ ┌───┐ │CF-│
1473 * [link 1] │ to C│ │RTS│ │ to A│ ││RTS│ │BAR│ │End│
1474 * ───────────────────────────────┴─────┴┬──┬┴───┴┬───┬┴─────┴┬──┬┴───┴┬───┬┴───┴┬──┬┴───┴─
1475 * │BA│ │CTS│ │BA│ │CTS│ │BA│
1476 * └──┘ └───┘ └──x └───┘ └──┘
1477 */
1478 if (m_nEmlsrStations == 2 && m_apMac->GetNLinks() == m_emlsrLinks.size())
1479 {
1480 // the following checks are only done with 2 EMLSR clients having no non-EMLSR link
1481 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
1482 {
1483 NS_TEST_EXPECT_MSG_GT_OR_EQ(frameExchanges.at(i).size(),
1484 2,
1485 "Expected at least 2 frame exchange sequences "
1486 << "involving EMLSR client " << i);
1487 // the first frame exchange must start with an ICF
1488 auto firstExchangeIt = frameExchanges.at(i).begin();
1489
1490 NS_TEST_EXPECT_MSG_EQ(IsTrigger(firstExchangeIt->front()->psduMap),
1491 true,
1492 "Expected an MU-RTS TF as ICF of first frame exchange sequence");
1494 firstExchangeIt->back()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1495 true,
1496 "Expected a QoS data frame in the first frame exchange sequence");
1497 }
1498
1499 // the second frame exchange is the one that starts first
1500 auto secondExchangeIt = std::next(frameExchanges.at(0).begin())->front()->startTx <
1501 std::next(frameExchanges.at(1).begin())->front()->startTx
1502 ? std::next(frameExchanges.at(0).begin())
1503 : std::next(frameExchanges.at(1).begin());
1504 decltype(secondExchangeIt) thirdExchangeIt;
1505 std::size_t thirdExchangeStaId;
1506
1507 if (secondExchangeIt == std::next(frameExchanges.at(0).begin()))
1508 {
1509 thirdExchangeIt = std::next(frameExchanges.at(1).begin());
1510 thirdExchangeStaId = 1;
1511 }
1512 else
1513 {
1514 thirdExchangeIt = std::next(frameExchanges.at(0).begin());
1515 thirdExchangeStaId = 0;
1516 }
1517
1518 // the second frame exchange is not protected by the ICF and starts a SIFS after the end
1519 // of the previous one
1520 NS_TEST_EXPECT_MSG_EQ(IsTrigger(secondExchangeIt->front()->psduMap),
1521 false,
1522 "Expected no ICF for the second frame exchange sequence");
1524 secondExchangeIt->front()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1525 true,
1526 "Expected a QoS data frame in the second frame exchange sequence");
1527
1528 // the first two frame exchanges occur on the same link
1529 NS_TEST_EXPECT_MSG_EQ(+secondExchangeIt->front()->linkId,
1530 +frameExchanges.at(0).begin()->front()->linkId,
1531 "Expected the first two frame exchanges to occur on the same link");
1532
1533 auto bAckRespIt = std::prev(secondExchangeIt->front());
1534 NS_TEST_EXPECT_MSG_EQ(bAckRespIt->psduMap.cbegin()->second->GetHeader(0).IsBlockAck(),
1535 true,
1536 "Expected a BlockAck response before the second frame exchange");
1537 auto bAckRespTxEnd =
1538 bAckRespIt->startTx +
1539 WifiPhy::CalculateTxDuration(bAckRespIt->psduMap,
1540 bAckRespIt->txVector,
1541 m_apMac->GetWifiPhy(bAckRespIt->linkId)->GetPhyBand());
1542
1543 // the second frame exchange starts a SIFS after the previous one
1545 bAckRespTxEnd + m_apMac->GetWifiPhy(bAckRespIt->linkId)->GetSifs(),
1546 secondExchangeIt->front()->startTx,
1547 "Expected the second frame exchange to start a SIFS after the first one");
1548
1549 // the third frame exchange is protected by MU-RTS and occurs on a different link
1550 NS_TEST_EXPECT_MSG_EQ(IsTrigger(thirdExchangeIt->front()->psduMap),
1551 true,
1552 "Expected an MU-RTS as ICF for the third frame exchange sequence");
1554 thirdExchangeIt->back()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1555 true,
1556 "Expected a QoS data frame in the third frame exchange sequence");
1557
1559 +secondExchangeIt->front()->linkId,
1560 +thirdExchangeIt->front()->linkId,
1561 "Expected the second and third frame exchanges to occur on distinct links");
1562
1563 auto secondQosIt = secondExchangeIt->front();
1564 auto secondQosTxEnd =
1565 secondQosIt->startTx +
1566 WifiPhy::CalculateTxDuration(secondQosIt->psduMap,
1567 secondQosIt->txVector,
1568 m_apMac->GetWifiPhy(secondQosIt->linkId)->GetPhyBand());
1569
1570 NS_TEST_EXPECT_MSG_GT_OR_EQ(thirdExchangeIt->front()->startTx,
1571 secondQosTxEnd + m_transitionDelay.at(thirdExchangeStaId),
1572 "Transmission started before transition delay");
1573
1574 // the BlockAck of the third frame exchange is not received correctly, so there should be
1575 // another frame exchange
1576 NS_TEST_EXPECT_MSG_EQ((thirdExchangeIt != frameExchanges.at(thirdExchangeStaId).end()),
1577 true,
1578 "Expected a fourth frame exchange");
1579 auto fourthExchangeIt = std::next(thirdExchangeIt);
1580
1581 // the fourth frame exchange is protected by MU-RTS
1582 NS_TEST_EXPECT_MSG_EQ(IsTrigger(fourthExchangeIt->front()->psduMap),
1583 true,
1584 "Expected an MU-RTS as ICF for the fourth frame exchange sequence");
1585
1586 bAckRespIt = std::prev(fourthExchangeIt->front());
1587 NS_TEST_EXPECT_MSG_EQ(bAckRespIt->psduMap.cbegin()->second->GetHeader(0).IsBlockAck(),
1588 true,
1589 "Expected a BlockAck response before the fourth frame exchange");
1590 auto phy = m_apMac->GetWifiPhy(bAckRespIt->linkId);
1591 bAckRespTxEnd = bAckRespIt->startTx + WifiPhy::CalculateTxDuration(bAckRespIt->psduMap,
1592 bAckRespIt->txVector,
1593 phy->GetPhyBand());
1594 auto timeout = phy->GetSifs() + phy->GetSlot() + MicroSeconds(20);
1595
1596 // the fourth frame exchange starts a PIFS after the previous one because the AP
1597 // performs PIFS recovery (the initial frame in the TXOP was successfully received by
1598 // a non-EMLSR client)
1599 NS_TEST_EXPECT_MSG_GT_OR_EQ(fourthExchangeIt->front()->startTx,
1600 bAckRespTxEnd + phy->GetPifs(),
1601 "Transmission started less than a PIFS after BlockAck");
1602 NS_TEST_EXPECT_MSG_LT(fourthExchangeIt->front()->startTx,
1603 bAckRespTxEnd + phy->GetPifs() +
1604 MicroSeconds(1) /* propagation delay upper bound */,
1605 "Transmission started too much time after BlockAck");
1606
1607 auto bAckReqIt = std::next(fourthExchangeIt->front(), 2);
1608 NS_TEST_EXPECT_MSG_EQ(bAckReqIt->psduMap.cbegin()->second->GetHeader(0).IsBlockAckReq(),
1609 true,
1610 "Expected a BlockAck request in the fourth frame exchange");
1611
1612 // we are done with processing the frame exchanges, remove them (two frame exchanges
1613 // per EMLSR client, plus the last one)
1614 frameExchanges.at(0).pop_front();
1615 frameExchanges.at(0).pop_front();
1616 frameExchanges.at(1).pop_front();
1617 frameExchanges.at(1).pop_front();
1618 frameExchanges.at(thirdExchangeStaId).pop_front();
1619 }
1620
1621 /**
1622 * After disabling EMLSR mode, no MU-RTS TF should be sent. After the exchange of
1623 * EML Operating Mode Notification frames, a number of packets are generated at the AP MLD
1624 * to prepare two A-MPDUs for each EMLSR client.
1625 *
1626 * EMLSR client with EMLSR mode to be enabled on all links (A is the EMLSR client, B is the
1627 * non-EMLSR client):
1628 *
1629 * [link 0] | power save mode
1630 * ────────────────────────────────────────────────────────
1631 * ┌─────┬─────┐ ┌──────┬──────┐
1632 * │QoS 8│QoS 9│ │QoS 10│QoS 11│
1633 * │ to A│ to A│ │ to A │ to A │
1634 * ┌───┐ ┌───┐ ├─────┼─────┤ ├──────┼──────┤
1635 * ┌───┐ │MU │ │EML│ │QoS 8│QoS 9│ │QoS 10│QoS 11│
1636 * [link 1] │ACK│ │RTS│ │OM │ │ to B│ to B│ │ to B │ to B │
1637 * ────┬───┬┴───┴──┴───┴┬───┬┴───┴┬───┬──┴─────┴─────┴┬──┬────┴──────┴──────┴┬──┬─────
1638 * │EML│ │CTS│ │ACK│ │BA│ │BA│
1639 * │OM │ └───┘ └───┘ ├──┤ ├──┤
1640 * └───┘ │BA│ │BA│
1641 * └──┘ └──┘
1642 *
1643 * [link 2] | power save mode
1644 * ────────────────────────────────────────────────────────────────────────────
1645 *
1646 *
1647 * EMLSR client with EMLSR mode to be enabled on not all the links (A is the EMLSR client,
1648 * B is the non-EMLSR client):
1649 * ┌─────┬─────┐
1650 * │QoS 8│QoS 9│
1651 * │ to A│ to A│
1652 * ├─────┼─────┤
1653 * │QoS 8│QoS 9│
1654 * [link 0 - non EMLSR] │ to B│ to B│
1655 * ─────────────────────────────────────────┴─────┴─────┴┬──┬─────────────
1656 * │BA│
1657 * ├──┤
1658 * │BA│
1659 * └──┘
1660 * ┌──────┬──────┐
1661 * │QoS 10│QoS 11│
1662 * │ to A │ to A │
1663 * ┌───┐ ┌───┐ ├──────┼──────┤
1664 * ┌───┐ │MU │ │EML│ │QoS 10│QoS 11│
1665 * [link 1] │ACK│ │RTS│ │OM │ │ to B │ to B │
1666 * ────┬───┬┴───┴──┴───┴┬───┬┴───┴┬───┬──┴──────┴──────┴┬──┬─────
1667 * │EML│ │CTS│ │ACK│ │BA│
1668 * │OM │ └───┘ └───┘ ├──┤
1669 * └───┘ │BA│
1670 * └──┘
1671 *
1672 * [link 2] | power save mode
1673 * ────────────────────────────────────────────────────────────────────────────
1674 *
1675 */
1676
1677 // for each EMLSR client, there should be a frame exchange with ICF and no data frame
1678 // (ICF protects the EML Notification response) if the EML Notification response is sent
1679 // while EMLSR mode is still enabled and two frame exchanges with data frames
1680 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
1681 {
1682 // the default EMLSR Manager requests to send EML Notification frames on the link where
1683 // the main PHY is operating; if EMLSR mode is still enabled on this link when the AP MLD
1684 // sends the EML Notification response, the latter is protected by an ICF
1685 auto exchangeIt = frameExchanges.at(i).cbegin();
1686
1687 auto linkIdOpt = m_staMacs[i]->GetLinkForPhy(m_mainPhyId);
1688 NS_TEST_ASSERT_MSG_EQ(linkIdOpt.has_value(),
1689 true,
1690 "Didn't find a link on which the main PHY is operating");
1691
1692 if (IsTrigger(exchangeIt->front()->psduMap))
1693 {
1694 NS_TEST_EXPECT_MSG_EQ(+exchangeIt->front()->linkId,
1695 +linkIdOpt.value(),
1696 "ICF was not sent on the expected link");
1697 NS_TEST_EXPECT_MSG_EQ(exchangeIt->size(),
1698 1,
1699 "Expected no data frame in the first frame exchange sequence");
1700 frameExchanges.at(i).pop_front();
1701 }
1702
1703 NS_TEST_EXPECT_MSG_GT_OR_EQ(frameExchanges.at(i).size(),
1704 2,
1705 "Expected at least 2 frame exchange sequences "
1706 << "involving EMLSR client " << i);
1707
1708 auto firstExchangeIt = frameExchanges.at(i).cbegin();
1709 auto secondExchangeIt = std::next(firstExchangeIt);
1710
1711 const auto firstAmpduTxEnd =
1712 firstExchangeIt->back()->startTx +
1714 firstExchangeIt->back()->psduMap,
1715 firstExchangeIt->back()->txVector,
1716 m_staMacs[i]->GetWifiPhy(firstExchangeIt->back()->linkId)->GetPhyBand());
1717 const auto secondAmpduTxStart = secondExchangeIt->front()->startTx;
1718
1720 firstExchangeIt->front()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1721 true,
1722 "Expected a QoS data frame in the first frame exchange sequence");
1723 NS_TEST_EXPECT_MSG_EQ(firstExchangeIt->size(),
1724 1,
1725 "Expected one frame only in the first frame exchange sequence");
1726
1728 secondExchangeIt->front()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
1729 true,
1730 "Expected a QoS data frame in the second frame exchange sequence");
1731 NS_TEST_EXPECT_MSG_EQ(secondExchangeIt->size(),
1732 1,
1733 "Expected one frame only in the second frame exchange sequence");
1734
1735 if (m_staMacs[i]->GetNLinks() == m_emlsrLinks.size())
1736 {
1737 // all links are EMLSR links: the two QoS data frames are sent one after another on
1738 // the link used for sending EML OMN
1740 +firstExchangeIt->front()->linkId,
1741 +linkIdOpt.value(),
1742 "First frame exchange expected to occur on link used to send EML OMN");
1743
1745 +secondExchangeIt->front()->linkId,
1746 +linkIdOpt.value(),
1747 "Second frame exchange expected to occur on link used to send EML OMN");
1748
1749 NS_TEST_EXPECT_MSG_LT(firstAmpduTxEnd,
1750 secondAmpduTxStart,
1751 "A-MPDUs are not sent one after another");
1752 }
1753 else
1754 {
1755 // the two QoS data frames are sent concurrently on distinct links
1756 NS_TEST_EXPECT_MSG_NE(+firstExchangeIt->front()->linkId,
1757 +secondExchangeIt->front()->linkId,
1758 "Frame exchanges expected to occur on distinct links");
1759
1760 NS_TEST_EXPECT_MSG_GT(firstAmpduTxEnd,
1761 secondAmpduTxStart,
1762 "A-MPDUs are not sent concurrently");
1763 }
1764 }
1765}
1766
1767void
1769{
1770 std::optional<std::size_t> staId;
1771 for (std::size_t id = 0; id < m_nEmlsrStations + m_nNonEmlsrStations; id++)
1772 {
1773 if (m_staMacs.at(id)->GetLinkIdByAddress(address))
1774 {
1775 staId = id;
1776 break;
1777 }
1778 }
1779 NS_TEST_ASSERT_MSG_EQ(staId.has_value(), true, "Not an address of a non-AP MLD " << address);
1780
1781 // check that all EMLSR links (but the link used for ML setup) of the EMLSR clients
1782 // are considered to be in power save mode by the AP MLD; all the other links have
1783 // transitioned to active mode instead
1784 for (uint8_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
1785 {
1786 bool psModeExpected =
1787 *staId < m_nEmlsrStations && linkId != m_mainPhyId && m_emlsrLinks.count(linkId) == 1;
1788 auto addr = m_staMacs.at(*staId)->GetAddress();
1789 auto psMode = m_apMac->GetWifiRemoteStationManager(linkId)->IsInPsMode(addr);
1790 NS_TEST_EXPECT_MSG_EQ(psMode,
1791 psModeExpected,
1792 "EMLSR link " << +linkId << " of EMLSR client " << *staId
1793 << " not in " << (psModeExpected ? "PS" : "active")
1794 << " mode");
1795 // check that AP is blocking transmission of QoS data frames on this link
1797 addr,
1798 linkId,
1799 WifiQueueBlockedReason::POWER_SAVE_MODE,
1800 psModeExpected,
1801 "Checking PM mode after association on AP MLD for EMLSR client " +
1802 std::to_string(*staId),
1803 false);
1804 }
1805}
1806
1807void
1809 const WifiTxVector& txVector,
1810 uint8_t linkId)
1811{
1812 // the AP is replying to a received EMLSR Notification frame
1813 auto pkt = mpdu->GetPacket()->Copy();
1814 const auto& hdr = mpdu->GetHeader();
1816 MgtEmlOmn frame;
1817 pkt->RemoveHeader(frame);
1818
1819 std::optional<std::size_t> staId;
1820 for (std::size_t id = 0; id < m_nEmlsrStations; id++)
1821 {
1822 if (m_staMacs.at(id)->GetFrameExchangeManager(linkId)->GetAddress() == hdr.GetAddr1())
1823 {
1824 staId = id;
1825 break;
1826 }
1827 }
1828 NS_TEST_ASSERT_MSG_EQ(staId.has_value(),
1829 true,
1830 "Not an address of an EMLSR client " << hdr.GetAddr1());
1831
1832 // The EMLSR mode change occurs a Transition Timeout after the end of the PPDU carrying the Ack
1833 auto phy = m_apMac->GetWifiPhy(linkId);
1834 auto txDuration =
1835 WifiPhy::CalculateTxDuration(mpdu->GetSize() + 4, // A-MPDU Subframe header size
1836 txVector,
1837 phy->GetPhyBand());
1838 WifiTxVector ackTxVector =
1839 m_staMacs.at(*staId)->GetWifiRemoteStationManager(linkId)->GetAckTxVector(hdr.GetAddr2(),
1840 txVector);
1841 auto ackDuration = WifiPhy::CalculateTxDuration(GetAckSize() + 4, // A-MPDU Subframe header
1842 ackTxVector,
1843 phy->GetPhyBand());
1844
1845 Simulator::Schedule(txDuration + phy->GetSifs() + ackDuration, [=, this]() {
1846 if (frame.m_emlControl.emlsrMode == 1)
1847 {
1848 // EMLSR mode enabled. Check that all EMLSR links of the EMLSR clients are considered
1849 // to be in active mode by the AP MLD
1850 for (const auto linkId : m_emlsrLinks)
1851 {
1852 auto addr = m_staMacs.at(*staId)->GetAddress();
1853 auto psMode = m_apMac->GetWifiRemoteStationManager(linkId)->IsInPsMode(addr);
1854 NS_TEST_EXPECT_MSG_EQ(psMode,
1855 false,
1856 "EMLSR link " << +linkId << " of EMLSR client " << *staId
1857 << " not in active mode");
1858 // check that AP is not blocking transmission of QoS data frames on this link
1859 CheckBlockedLink(
1860 m_apMac,
1861 addr,
1862 linkId,
1863 WifiQueueBlockedReason::POWER_SAVE_MODE,
1864 false,
1865 "Checking EMLSR links on AP MLD after EMLSR mode is enabled on EMLSR client " +
1866 std::to_string(*staId),
1867 false);
1868 }
1869 }
1870 else
1871 {
1872 // EMLSR mode disabled. Check that all EMLSR links (but the link used to send the
1873 // EML Notification frame) of the EMLSR clients are considered to be in power save
1874 // mode by the AP MLD; the other links are in active mode
1875 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1876 {
1877 bool psModeExpected = id != linkId && m_emlsrLinks.count(id) == 1;
1878 auto addr = m_staMacs.at(*staId)->GetAddress();
1879 auto psMode = m_apMac->GetWifiRemoteStationManager(id)->IsInPsMode(addr);
1880 NS_TEST_EXPECT_MSG_EQ(psMode,
1881 psModeExpected,
1882 "EMLSR link "
1883 << +id << " of EMLSR client " << *staId << " not in "
1884 << (psModeExpected ? "PS" : "active") << " mode");
1885 // check that AP is blocking transmission of QoS data frames on this link
1886 CheckBlockedLink(
1887 m_apMac,
1888 addr,
1889 id,
1890 WifiQueueBlockedReason::POWER_SAVE_MODE,
1891 psModeExpected,
1892 "Checking links on AP MLD after EMLSR mode is disabled on EMLSR client " +
1893 std::to_string(*staId),
1894 false);
1895 }
1896 }
1897 });
1898}
1899
1900void
1902 const WifiTxVector& txVector,
1903 uint8_t linkId)
1904{
1905 // an EMLSR client is sending an EMLSR Notification frame
1906 auto pkt = mpdu->GetPacket()->Copy();
1907 const auto& hdr = mpdu->GetHeader();
1909 MgtEmlOmn frame;
1910 pkt->RemoveHeader(frame);
1911
1912 std::optional<std::size_t> staId;
1913 for (std::size_t id = 0; id < m_nEmlsrStations; id++)
1914 {
1915 if (m_staMacs.at(id)->GetFrameExchangeManager(linkId)->GetAddress() == hdr.GetAddr2())
1916 {
1917 staId = id;
1918 break;
1919 }
1920 }
1921 NS_TEST_ASSERT_MSG_EQ(staId.has_value(),
1922 true,
1923 "Not an address of an EMLSR client " << hdr.GetAddr1());
1924
1925 auto phy = m_staMacs.at(*staId)->GetWifiPhy(linkId);
1926 auto txDuration = WifiPhy::CalculateTxDuration(mpdu->GetSize(), txVector, phy->GetPhyBand());
1927 auto ackTxVector =
1928 m_apMac->GetWifiRemoteStationManager(linkId)->GetAckTxVector(hdr.GetAddr2(), txVector);
1929 auto ackDuration = WifiPhy::CalculateTxDuration(GetAckSize(), ackTxVector, phy->GetPhyBand());
1930 auto cfEndDuration = WifiPhy::CalculateTxDuration(
1932 m_staMacs.at(*staId)->GetWifiRemoteStationManager(linkId)->GetRtsTxVector(
1934 txVector.GetChannelWidth()),
1935 phy->GetPhyBand());
1936
1937 if (frame.m_emlControl.emlsrMode != 0)
1938 {
1939 return;
1940 }
1941
1942 // EMLSR mode disabled
1943 auto timeToCfEnd = txDuration + phy->GetSifs() + ackDuration + phy->GetSifs() + cfEndDuration;
1944
1945 // before the end of the CF-End frame, this link only is not blocked on both the
1946 // EMLSR client and the AP MLD
1947 Simulator::Schedule(timeToCfEnd - MicroSeconds(1), [=, this]() {
1948 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1949 {
1950 CheckBlockedLink(m_staMacs.at(*staId),
1951 m_apMac->GetAddress(),
1952 id,
1953 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1954 id != linkId && m_staMacs.at(*staId)->IsEmlsrLink(id),
1955 "Checking links on EMLSR client " + std::to_string(*staId) +
1956 " before the end of CF-End frame");
1958 m_staMacs.at(*staId)->GetAddress(),
1959 id,
1960 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1961 id != linkId && m_staMacs.at(*staId)->IsEmlsrLink(id),
1962 "Checking links of EMLSR client " + std::to_string(*staId) +
1963 " on the AP MLD before the end of CF-End frame");
1964 }
1965 });
1966 // after the end of the CF-End frame, all links for the EMLSR client are blocked on the
1967 // AP MLD
1968 Simulator::Schedule(timeToCfEnd + MicroSeconds(1), [=, this]() {
1969 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1970 {
1971 if (m_staMacs.at(*staId)->IsEmlsrLink(id))
1972 {
1973 CheckBlockedLink(
1974 m_apMac,
1975 m_staMacs.at(*staId)->GetAddress(),
1976 id && m_staMacs.at(*staId)->IsEmlsrLink(id),
1977 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
1978 true,
1979 "Checking links of EMLSR client " + std::to_string(*staId) +
1980 " are all blocked on the AP MLD right after the end of CF-End");
1981 }
1982 }
1983 });
1984 // before the end of the transition delay, all links for the EMLSR client are still
1985 // blocked on the AP MLD
1986 Simulator::Schedule(timeToCfEnd + m_transitionDelay.at(*staId) - MicroSeconds(1), [=, this]() {
1987 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1988 {
1989 if (m_staMacs.at(*staId)->IsEmlsrLink(id))
1990 {
1991 CheckBlockedLink(m_apMac,
1992 m_staMacs.at(*staId)->GetAddress(),
1993 id,
1994 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
1995 true,
1996 "Checking links of EMLSR client " + std::to_string(*staId) +
1997 " are all blocked on the AP MLD before the end of "
1998 "transition delay");
1999 }
2000 }
2001 });
2002 // immediately after the transition delay, all links for the EMLSR client are unblocked
2003 Simulator::Schedule(timeToCfEnd + m_transitionDelay.at(*staId) + MicroSeconds(1), [=, this]() {
2004 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2005 {
2006 if (m_staMacs.at(*staId)->IsEmlsrLink(id))
2007 {
2008 CheckBlockedLink(m_apMac,
2009 m_staMacs.at(*staId)->GetAddress(),
2010 id,
2011 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
2012 false,
2013 "Checking links of EMLSR client " + std::to_string(*staId) +
2014 " are all unblocked on the AP MLD after the transition delay");
2015 }
2016 }
2017 });
2018}
2019
2020void
2022 const WifiTxVector& txVector,
2023 uint8_t linkId)
2024{
2025 CtrlTriggerHeader trigger;
2026 mpdu->GetPacket()->PeekHeader(trigger);
2027 if (!trigger.IsMuRts())
2028 {
2029 return;
2030 }
2031
2033 true,
2034 "Did not expect an ICF before enabling EMLSR mode");
2035
2038 "Unexpected preamble type for the Initial Control frame");
2039 auto rate = txVector.GetMode().GetDataRate(txVector);
2040 NS_TEST_EXPECT_MSG_EQ((rate == 6e6 || rate == 12e6 || rate == 24e6),
2041 true,
2042 "Unexpected rate for the Initial Control frame: " << rate);
2043
2044 bool found = false;
2045 Time maxPaddingDelay{};
2046
2047 for (const auto& userInfo : trigger)
2048 {
2049 auto addr = m_apMac->GetMldOrLinkAddressByAid(userInfo.GetAid12());
2050 NS_TEST_ASSERT_MSG_EQ(addr.has_value(),
2051 true,
2052 "AID " << userInfo.GetAid12() << " not found");
2053
2054 if (m_apMac->GetWifiRemoteStationManager(linkId)->GetEmlsrEnabled(*addr))
2055 {
2056 found = true;
2057
2058 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
2059 {
2060 if (m_staMacs.at(i)->GetAddress() == *addr)
2061 {
2062 maxPaddingDelay = Max(maxPaddingDelay, m_paddingDelay.at(i));
2063 break;
2064 }
2065 }
2066
2067 // check that the AP has blocked transmission on all other EMLSR links
2068 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2069 {
2070 if (!m_apMac->GetWifiRemoteStationManager(id)->GetEmlsrEnabled(*addr))
2071 {
2072 continue;
2073 }
2074
2076 *addr,
2077 id,
2078 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2079 id != linkId,
2080 "Checking that AP blocked transmissions on all other EMLSR "
2081 "links after sending ICF to client with AID=" +
2082 std::to_string(userInfo.GetAid12()),
2083 false);
2084 }
2085 }
2086 }
2087
2088 NS_TEST_EXPECT_MSG_EQ(found, true, "Expected ICF to be addressed to at least an EMLSR client");
2089
2090 auto txDuration = WifiPhy::CalculateTxDuration(mpdu->GetSize(),
2091 txVector,
2092 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2093
2094 if (maxPaddingDelay.IsStrictlyPositive())
2095 {
2096 // compare the TX duration of this Trigger Frame to that of the Trigger Frame with no
2097 // padding added
2098 trigger.SetPaddingSize(0);
2099 auto pkt = Create<Packet>();
2100 pkt->AddHeader(trigger);
2101 auto txDurationWithout =
2102 WifiPhy::CalculateTxDuration(Create<WifiPsdu>(pkt, mpdu->GetHeader()),
2103 txVector,
2104 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2105
2106 NS_TEST_EXPECT_MSG_EQ(txDuration,
2107 txDurationWithout + maxPaddingDelay,
2108 "Unexpected TX duration of the MU-RTS TF with padding "
2109 << maxPaddingDelay.As(Time::US));
2110 }
2111
2112 // check that the EMLSR clients have blocked transmissions on other links, switched their main
2113 // PHY (if needed) and have put aux PHYs to sleep after receiving this ICF
2114 for (const auto& userInfo : trigger)
2115 {
2116 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
2117 {
2118 if (m_staMacs[i]->GetAssociationId() != userInfo.GetAid12())
2119 {
2120 continue;
2121 }
2122
2123 const auto mainPhyLinkId = m_staMacs[i]->GetLinkForPhy(m_mainPhyId);
2124
2125 Simulator::Schedule(txDuration + NanoSeconds(5), [=, this]() {
2126 for (uint8_t id = 0; id < m_staMacs[i]->GetNLinks(); id++)
2127 {
2128 // non-EMLSR links or links on which ICF is received are not blocked
2130 m_apMac->GetAddress(),
2131 id,
2132 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2133 id != linkId && m_staMacs[i]->IsEmlsrLink(id),
2134 "Checking EMLSR links on EMLSR client " + std::to_string(i) +
2135 " after receiving ICF");
2136 }
2137
2138 if (mainPhyLinkId != linkId)
2139 {
2140 CheckMainPhyTraceInfo(i, "DlTxopIcfReceivedByAuxPhy", mainPhyLinkId, linkId);
2141 }
2142
2144 });
2145
2146 break;
2147 }
2148 }
2149}
2150
2151void
2153 const WifiTxVector& txVector,
2154 uint8_t linkId)
2155{
2156 if (m_nEmlsrStations != 2 || m_apMac->GetNLinks() != m_emlsrLinks.size() ||
2158
2159 {
2160 // we are interested in frames sent to test transition delay
2161 return;
2162 }
2163
2164 std::size_t firstClientId = 0;
2165 std::size_t secondClientId = 1;
2166 auto addr = m_staMacs[secondClientId]->GetAddress();
2167 auto txDuration =
2168 WifiPhy::CalculateTxDuration(psduMap, txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2169
2171
2172 switch (m_countQoSframes)
2173 {
2174 case 1:
2175 // generate another small packet addressed to the first EMLSR client only
2176 m_apMac->GetDevice()->GetNode()->AddApplication(
2177 GetApplication(DOWNLINK, firstClientId, 1, 40));
2178 // both EMLSR clients are about to receive a QoS data frame
2179 for (std::size_t clientId : {firstClientId, secondClientId})
2180 {
2181 Simulator::Schedule(txDuration, [=, this]() {
2182 for (uint8_t id = 0; id < m_staMacs[clientId]->GetNLinks(); id++)
2183 {
2184 // link on which QoS data is received is not blocked
2185 CheckBlockedLink(m_staMacs[clientId],
2186 m_apMac->GetAddress(),
2187 id,
2188 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2189 id != linkId,
2190 "Checking EMLSR links on EMLSR client " +
2191 std::to_string(clientId) +
2192 " after receiving the first QoS data frame");
2193 }
2194 });
2195 }
2196 break;
2197 case 2:
2198 // generate another small packet addressed to the second EMLSR client
2199 m_apMac->GetDevice()->GetNode()->AddApplication(
2200 GetApplication(DOWNLINK, secondClientId, 1, 40));
2201
2202 // when the transmission of the second QoS data frame starts, both EMLSR clients are
2203 // still blocking all the links but the one used to receive the QoS data frame
2204 for (std::size_t clientId : {firstClientId, secondClientId})
2205 {
2206 for (uint8_t id = 0; id < m_staMacs[clientId]->GetNLinks(); id++)
2207 {
2208 // link on which QoS data is received is not blocked
2209 CheckBlockedLink(m_staMacs[clientId],
2210 m_apMac->GetAddress(),
2211 id,
2212 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2213 id != linkId,
2214 "Checking EMLSR links on EMLSR client " +
2215 std::to_string(clientId) +
2216 " when starting the reception of the second QoS frame");
2217 }
2218 }
2219
2220 // the EMLSR client that is not the recipient of the QoS frame being transmitted will
2221 // switch back to listening mode after a transition delay starting from the end of
2222 // the PPDU carrying this QoS data frame
2223
2224 // immediately before the end of the PPDU, this link only is not blocked for the EMLSR
2225 // client on the AP MLD
2226 Simulator::Schedule(txDuration - NanoSeconds(1), [=, this]() {
2227 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2228 {
2230 addr,
2231 id,
2232 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2233 id != linkId,
2234 "Checking that links of EMLSR client " +
2235 std::to_string(secondClientId) +
2236 " are blocked on the AP MLD before the end of the PPDU");
2237 }
2238 });
2239 // immediately before the end of the PPDU, all the links on the EMLSR client that is not
2240 // the recipient of the second QoS frame are unblocked (they are unblocked when the
2241 // PHY-RXSTART.indication is not received)
2242 Simulator::Schedule(txDuration - NanoSeconds(1), [=, this]() {
2243 for (uint8_t id = 0; id < m_staMacs[secondClientId]->GetNLinks(); id++)
2244 {
2245 CheckBlockedLink(m_staMacs[secondClientId],
2246 m_apMac->GetAddress(),
2247 id,
2248 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2249 false,
2250 "Checking that links of EMLSR client " +
2251 std::to_string(secondClientId) +
2252 " are unblocked before the end of the second QoS frame");
2253 }
2254 });
2255 // immediately after the end of the PPDU, all links are blocked for the EMLSR client
2256 Simulator::Schedule(txDuration + NanoSeconds(1), [=, this]() {
2257 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2258 {
2260 addr,
2261 id,
2262 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
2263 true,
2264 "Checking links of EMLSR client " +
2265 std::to_string(secondClientId) +
2266 " are all blocked on the AP MLD after the end of the PPDU");
2267 }
2268 });
2269 // immediately before the transition delay, all links are still blocked for the EMLSR client
2271 txDuration + m_transitionDelay.at(secondClientId) - NanoSeconds(1),
2272 [=, this]() {
2273 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2274 {
2276 m_apMac,
2277 addr,
2278 id,
2279 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
2280 true,
2281 "Checking links of EMLSR client " + std::to_string(secondClientId) +
2282 " are all blocked on the AP MLD before the transition delay",
2283 false);
2284 }
2285 });
2286
2287 // 100 us before the transition delay expires, generate another small packet addressed
2288 // to a non-EMLSR client. The AP will start a TXOP to transmit this frame, while the
2289 // frame addressed to the EMLSR client is still queued because the transition delay has
2290 // not yet elapsed. The transition delay will expire while the AP is transmitting the
2291 // frame to the non-EMLSR client, so that the AP continues the TXOP to transmit the frame
2292 // to the EMLSR client and we can check that the AP performs PIFS recovery after missing
2293 // the BlockAck from the EMLSR client
2294 Simulator::Schedule(txDuration + m_transitionDelay.at(secondClientId) - MicroSeconds(100),
2295 [=, this]() {
2296 m_apMac->GetDevice()->GetNode()->AddApplication(
2298 });
2299
2300 break;
2301 case 3:
2302 // this is the frame addressed to a non-EMLSR client, which is transmitted before the
2303 // frame addressed to the EMLSR client, because the links of the latter are still blocked
2304 // at the AP because the transition delay has not yet elapsed
2306 psduMap.cbegin()->second->GetAddr1(),
2307 m_staMacs[m_nEmlsrStations]->GetFrameExchangeManager(linkId)->GetAddress(),
2308 "QoS frame not addressed to a non-EMLSR client");
2309
2310 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2311 {
2313 addr,
2314 id,
2315 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
2316 true,
2317 "Checking links of EMLSR client " + std::to_string(secondClientId) +
2318 " are all blocked on the AP MLD before the transition delay");
2319 }
2320 // Block transmissions to the EMLSR client on all the links but the one on which this
2321 // frame is sent, so that the AP will continue this TXOP to send the queued frame to the
2322 // EMLSR client once the transition delay elapses
2323 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2324 {
2325 if (id != linkId)
2326 {
2327 m_apMac->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED, addr, {id});
2328 }
2329 }
2330 break;
2331 case 4:
2332 // the AP is continuing the TXOP, no need to block transmissions anymore
2333 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2334 {
2335 m_apMac->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED, addr, {id});
2336 }
2337 // at the end of the fourth QoS frame, this link only is not blocked on the EMLSR
2338 // client receiving the frame
2339 Simulator::Schedule(txDuration, [=, this]() {
2340 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2341 {
2342 CheckBlockedLink(m_staMacs[secondClientId],
2343 m_apMac->GetAddress(),
2344 id,
2345 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2346 id != linkId,
2347 "Checking EMLSR links on EMLSR client " +
2348 std::to_string(secondClientId) +
2349 " after receiving the fourth QoS data frame");
2350 }
2351 });
2352 break;
2353 }
2354}
2355
2356void
2358 const WifiTxVector& txVector,
2359 uint8_t phyId)
2360{
2361 if (m_nEmlsrStations != 2 || m_apMac->GetNLinks() != m_emlsrLinks.size() ||
2363 {
2364 // we are interested in frames sent to test transition delay
2365 return;
2366 }
2367
2368 if (++m_countBlockAck == 4)
2369 {
2370 // fourth BlockAck is sent by a non-EMLSR client
2371 return;
2372 }
2373
2374 auto taddr = psduMap.cbegin()->second->GetAddr2();
2375 std::size_t clientId;
2376 if (m_staMacs[0]->GetLinkIdByAddress(taddr))
2377 {
2378 clientId = 0;
2379 }
2380 else
2381 {
2382 NS_TEST_ASSERT_MSG_EQ(m_staMacs[1]->GetLinkIdByAddress(taddr).has_value(),
2383 true,
2384 "Unexpected TA for BlockAck: " << taddr);
2385 clientId = 1;
2386 }
2387
2388 // find the link on which the main PHY is operating
2389 auto currMainPhyLinkId = m_staMacs[clientId]->GetLinkForPhy(phyId);
2391 currMainPhyLinkId.has_value(),
2392 true,
2393 "Didn't find the link on which the PHY sending the BlockAck is operating");
2394 auto linkId = *currMainPhyLinkId;
2395
2396 // we need the MLD address to check the status of the container queues
2397 auto addr = m_apMac->GetWifiRemoteStationManager(linkId)->GetMldAddress(taddr);
2398 NS_TEST_ASSERT_MSG_EQ(addr.has_value(), true, "MLD address not found for " << taddr);
2399
2400 auto apPhy = m_apMac->GetWifiPhy(linkId);
2401 auto txDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, apPhy->GetPhyBand());
2402 auto cfEndTxDuration = WifiPhy::CalculateTxDuration(
2404 m_apMac->GetWifiRemoteStationManager(linkId)->GetRtsTxVector(Mac48Address::GetBroadcast(),
2405 txVector.GetChannelWidth()),
2406 apPhy->GetPhyBand());
2407
2408 switch (m_countBlockAck)
2409 {
2410 case 5:
2411 // the PPDU carrying this BlockAck is corrupted, hence the AP MLD MAC receives the
2412 // PHY-RXSTART indication but it does not receive any frame from the PHY. Therefore,
2413 // at the end of the PPDU transmission, the AP MLD realizes that the EMLSR client has
2414 // not responded and makes an attempt at continuing the TXOP
2415
2416 // at the end of the PPDU, this link only is not blocked on both the EMLSR client and
2417 // the AP MLD
2418 Simulator::Schedule(txDuration, [=, this]() {
2419 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2420 {
2421 CheckBlockedLink(m_staMacs[clientId],
2422 m_apMac->GetAddress(),
2423 id,
2424 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2425 id != linkId,
2426 "Checking links on EMLSR client " + std::to_string(clientId) +
2427 " at the end of fourth BlockAck");
2429 *addr,
2430 id,
2431 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2432 id != linkId,
2433 "Checking links of EMLSR client " + std::to_string(clientId) +
2434 " on the AP MLD at the end of fourth BlockAck");
2435 }
2436 });
2437 // a SIFS after the end of the PPDU, still this link only is not blocked on both the
2438 // EMLSR client and the AP MLD
2439 Simulator::Schedule(txDuration + apPhy->GetSifs(), [=, this]() {
2440 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2441 {
2442 CheckBlockedLink(m_staMacs[clientId],
2443 m_apMac->GetAddress(),
2444 id,
2445 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2446 id != linkId,
2447 "Checking links on EMLSR client " + std::to_string(clientId) +
2448 " a SIFS after the end of fourth BlockAck");
2449 CheckBlockedLink(m_apMac,
2450 *addr,
2451 id,
2452 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2453 id != linkId,
2454 "Checking links of EMLSR client " + std::to_string(clientId) +
2455 " a SIFS after the end of fourth BlockAck");
2456 }
2457 });
2458 // corrupt this BlockAck so that the AP MLD sends a BlockAckReq later on
2459 {
2460 auto uid = psduMap.cbegin()->second->GetPacket()->GetUid();
2461 m_errorModel->SetList({uid});
2462 }
2463 break;
2464 case 6:
2465 // at the end of the PPDU, this link only is not blocked on both the EMLSR client and
2466 // the AP MLD
2467 Simulator::Schedule(txDuration, [=, this]() {
2468 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2469 {
2470 CheckBlockedLink(m_staMacs[clientId],
2471 m_apMac->GetAddress(),
2472 id,
2473 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2474 id != linkId,
2475 "Checking links on EMLSR client " + std::to_string(clientId) +
2476 " at the end of fifth BlockAck");
2478 *addr,
2479 id,
2480 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2481 id != linkId,
2482 "Checking links of EMLSR client " + std::to_string(clientId) +
2483 " on the AP MLD at the end of fifth BlockAck");
2484 }
2485 });
2486 // before the end of the CF-End frame, still this link only is not blocked on both the
2487 // EMLSR client and the AP MLD
2489 txDuration + apPhy->GetSifs() + cfEndTxDuration - MicroSeconds(1),
2490 [=, this]() {
2491 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2492 {
2493 CheckBlockedLink(m_staMacs[clientId],
2494 m_apMac->GetAddress(),
2495 id,
2496 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2497 id != linkId,
2498 "Checking links on EMLSR client " + std::to_string(clientId) +
2499 " before the end of CF-End frame");
2501 *addr,
2502 id,
2503 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2504 id != linkId,
2505 "Checking links of EMLSR client " + std::to_string(clientId) +
2506 " on the AP MLD before the end of CF-End frame");
2507 }
2508 });
2509 // after the end of the CF-End frame, all links for the EMLSR client are blocked on the
2510 // AP MLD
2512 txDuration + apPhy->GetSifs() + cfEndTxDuration + MicroSeconds(1),
2513 [=, this]() {
2514 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2515 {
2517 m_apMac,
2518 *addr,
2519 id,
2520 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
2521 true,
2522 "Checking links of EMLSR client " + std::to_string(clientId) +
2523 " are all blocked on the AP MLD right after the end of CF-End");
2524 }
2525 });
2526 // before the end of the transition delay, all links for the EMLSR client are still
2527 // blocked on the AP MLD
2529 txDuration + apPhy->GetSifs() + cfEndTxDuration + m_transitionDelay.at(clientId) -
2530 MicroSeconds(1),
2531 [=, this]() {
2532 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2533 {
2535 m_apMac,
2536 *addr,
2537 id,
2538 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
2539 true,
2540 "Checking links of EMLSR client " + std::to_string(clientId) +
2541 " are all blocked on the AP MLD before the end of transition delay");
2542 }
2543 });
2544 // immediately after the transition delay, all links for the EMLSR client are unblocked
2546 txDuration + apPhy->GetSifs() + cfEndTxDuration + m_transitionDelay.at(clientId) +
2547 MicroSeconds(1),
2548 [=, this]() {
2549 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
2550 {
2552 m_apMac,
2553 *addr,
2554 id,
2555 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
2556 false,
2557 "Checking links of EMLSR client " + std::to_string(clientId) +
2558 " are all unblocked on the AP MLD after the transition delay");
2559 }
2560 });
2561 break;
2562 }
2563}
2564
2565void
2575
2577 : EmlsrOperationsTestBase("Check EML UL TXOP transmissions (genBackoffAndUseAuxPhyCca=" +
2578 std::to_string(params.genBackoffAndUseAuxPhyCca) +
2579 ", nSlotsLeftAlert=" + std::to_string(params.nSlotsLeftAlert)),
2580 m_emlsrLinks(params.linksToEnableEmlsrOn),
2581 m_channelWidth(params.channelWidth),
2582 m_auxPhyChannelWidth(params.auxPhyChannelWidth),
2583 m_mediumSyncDuration(params.mediumSyncDuration),
2584 m_msdMaxNTxops(params.msdMaxNTxops),
2585 m_emlsrEnabledTime(0),
2586 m_firstUlPktsGenTime(0),
2587 m_unblockMainPhyLinkDelay(MilliSeconds(20)),
2588 m_checkBackoffStarted(false),
2589 m_countQoSframes(0),
2590 m_countBlockAck(0),
2591 m_countRtsframes(0),
2592 m_genBackoffIfTxopWithoutTx(params.genBackoffAndUseAuxPhyCca),
2593 m_useAuxPhyCca(params.genBackoffAndUseAuxPhyCca),
2594 m_nSlotsLeftAlert(params.nSlotsLeftAlert),
2595 m_switchMainPhyBackDelayTimeout(params.switchMainPhyBackDelayTimeout)
2596{
2597 m_nEmlsrStations = 1;
2599 m_linksToEnableEmlsrOn = params.linksToEnableEmlsrOn;
2600 m_mainPhyId = 1;
2601
2602 // when aux PHYs do not switch link, the main PHY switches back to its previous link after
2603 // a TXOP, hence the transition delay must exceed the channel switch delay (default: 250us)
2605 m_establishBaDl = true;
2606 m_establishBaUl = true;
2607 m_putAuxPhyToSleep = params.putAuxPhyToSleep;
2608 m_duration = Seconds(1);
2609
2610 NS_ABORT_MSG_IF(params.linksToEnableEmlsrOn.size() < 2,
2611 "This test requires at least two links to be configured as EMLSR links");
2612 for (uint8_t id = 0; id < 3; id++)
2613 {
2614 if (!m_emlsrLinks.contains(id))
2615 {
2616 // non-EMLSR link found
2617 m_nonEmlsrLink = id;
2618 break;
2619 }
2620 }
2621}
2622
2623void
2625{
2626 Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth",
2628 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(false));
2629 Config::SetDefault("ns3::AdvancedEmlsrManager::UseAuxPhyCca", BooleanValue(m_useAuxPhyCca));
2630 Config::SetDefault("ns3::EhtConfiguration::MediumSyncDuration",
2632 Config::SetDefault("ns3::EhtConfiguration::MsdMaxNTxops", UintegerValue(m_msdMaxNTxops));
2633 Config::SetDefault("ns3::ChannelAccessManager::GenerateBackoffIfTxopWithoutTx",
2635 Config::SetDefault("ns3::ChannelAccessManager::NSlotsLeft", UintegerValue(m_nSlotsLeftAlert));
2636 // Channel switch delay should be less than RTS TX time + SIFS + CTS TX time, otherwise
2637 // UL TXOPs cannot be initiated by aux PHYs
2638 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(75)));
2639 Config::SetDefault("ns3::WifiPhy::NotifyMacHdrRxEnd", BooleanValue(true));
2640
2642
2643 m_staMacs[0]->GetQosTxop(AC_BE)->TraceConnectWithoutContext(
2644 "BackoffTrace",
2646
2647 uint8_t linkId = 0;
2648 // configure channels of the given width
2650 {
2651 MHz_u bw{20};
2652 uint8_t number = band == WIFI_PHY_BAND_5GHZ ? 36 : 1;
2653
2654 auto width =
2655 std::min(m_channelWidth, band == WIFI_PHY_BAND_2_4GHZ ? MHz_u{40} : MHz_u{160});
2656 while (bw < width)
2657 {
2658 bw *= 2;
2659 number += Count20MHzSubchannels(bw);
2660 }
2661
2662 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0]})
2663 {
2664 mac->GetWifiPhy(linkId)->SetOperatingChannel(
2665 WifiPhy::ChannelTuple{number, width, band, 0});
2666 }
2667 linkId++;
2668 }
2669
2670 // install post reception error model on the AP affiliated with the AP MLD and operating on
2671 // the same link as the main PHY of the EMLSR client
2673 m_apMac->GetWifiPhy(m_mainPhyId)->SetPostReceptionErrorModel(m_errorModel);
2674}
2675
2676void
2678{
2679 NS_LOG_INFO("Backoff value " << backoff << " generated by EMLSR client on link " << +linkId
2680 << "\n");
2681 if (linkId != m_mainPhyId)
2682 {
2683 return; // we are only interested in backoff on main PHY link
2684 }
2685
2686 if (m_backoffEndTime)
2687 {
2689 {
2690 // another backoff value while checkBackoffStarted is true is generated only if
2691 // GenerateBackoffIfTxopWithoutTx is true
2694 true,
2695 "Another backoff value should not be generated while the main PHY link is blocked");
2696
2699 "Backoff generated at unexpected time");
2700 }
2701 else
2702 {
2703 // we are done checking the backoff
2704 m_backoffEndTime.reset();
2705 }
2706 }
2707
2709 {
2710 if (!m_backoffEndTime.has_value())
2711 {
2712 // this is the first time we set m_backoffEndTime, which is done right after receiving
2713 // a BlockAck, thus we have to wait an AIFS before invoking backoff
2715 m_staMacs[0]->GetChannelAccessManager(linkId)->GetSifs() +
2716 m_staMacs[0]->GetQosTxop(AC_BE)->GetAifsn(linkId) *
2717 m_staMacs[0]->GetChannelAccessManager(linkId)->GetSlot();
2718 }
2719 else
2720 {
2721 // we get here when the backoff expired but no transmission occurred, thus we have
2722 // generated a new backoff value and we will start decrementing the counter in a slot
2724 Simulator::Now() + m_staMacs[0]->GetChannelAccessManager(linkId)->GetSlot();
2725 }
2726 // add the time corresponding to the generated number of slots
2727 m_backoffEndTime.value() +=
2728 backoff * m_staMacs[0]->GetChannelAccessManager(linkId)->GetSlot();
2729 NS_LOG_DEBUG("Expected backoff end time = " << m_backoffEndTime->As(Time::US) << "\n");
2730 }
2731}
2732
2733void
2735 uint8_t phyId,
2736 WifiConstPsduMap psduMap,
2737 WifiTxVector txVector,
2738 double txPowerW)
2739{
2740 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2741 auto linkId = m_txPsdus.back().linkId;
2742
2743 auto psdu = psduMap.begin()->second;
2744 auto nodeId = mac->GetDevice()->GetNode()->GetId();
2745
2746 switch (psdu->GetHeader(0).GetType())
2747 {
2749 NS_ASSERT_MSG(nodeId > 0, "APs do not send AssocReq frames");
2750 NS_TEST_EXPECT_MSG_EQ(+linkId, +m_mainPhyId, "AssocReq not sent by the main PHY");
2751 break;
2752
2753 case WIFI_MAC_CTL_RTS:
2754 CheckRtsFrames(*psdu->begin(), txVector, linkId);
2755 break;
2756
2757 case WIFI_MAC_CTL_CTS:
2758 CheckCtsFrames(*psdu->begin(), txVector, linkId);
2759 break;
2760
2761 case WIFI_MAC_QOSDATA:
2762 CheckQosFrames(psduMap, txVector, linkId);
2763 break;
2764
2766 CheckBlockAck(psduMap, txVector, linkId);
2767 break;
2768
2769 default:;
2770 }
2771}
2772
2773void
2775{
2776 // initially, we prevent transmissions on aux PHY links
2777 auto auxPhyLinks = m_staMacs[0]->GetSetupLinkIds();
2778 auxPhyLinks.erase(m_mainPhyId);
2779 if (m_nonEmlsrLink)
2780 {
2781 auxPhyLinks.erase(*m_nonEmlsrLink);
2782 }
2783 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2784 m_apMac->GetAddress(),
2785 auxPhyLinks);
2786
2787 // Association, Block Ack agreement establishment and enabling EMLSR mode have been done.
2788 // After 50ms, schedule:
2789 // - block of transmissions on the link where the main PHY is operating and on the non-EMLSR
2790 // link (if any)
2791 // - the generation of two UL packets
2792 // - after m_unblockMainPhyLinkDelay, unblock transmissions on the link where the main PHY
2793 // is operating, so that the first data frame is transmitted on that link
2795 std::set<uint8_t> linkIds;
2796 linkIds.insert(*m_staMacs[0]->GetLinkForPhy(m_mainPhyId));
2797 if (m_nonEmlsrLink)
2798 {
2799 linkIds.insert(*m_nonEmlsrLink);
2800 }
2801 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2802 m_apMac->GetAddress(),
2803 linkIds);
2804
2805 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
2806 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
2808
2810 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2811 m_apMac->GetAddress(),
2812 {*m_staMacs[0]->GetLinkForPhy(m_mainPhyId)});
2813 });
2814 });
2815}
2816
2817void
2819 const WifiTxVector& txVector,
2820 uint8_t linkId)
2821{
2823
2824 auto txDuration =
2825 WifiPhy::CalculateTxDuration(psduMap, txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2826
2827 switch (m_countQoSframes)
2828 {
2829 case 1:
2830 case 2:
2831 // do nothing, these are the QoS data frames sent to establish BA agreements in DL and UL
2832 // direction
2833 break;
2834 case 3:
2835 // first UL data frame (transmitted by the main PHY)
2836 if (m_nonEmlsrLink)
2837 {
2838 // generate data packets for another UL data frame, which will be sent on the
2839 // non-EMLSR link
2840 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
2841 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
2842 GetApplication(UPLINK, 0, 2, 1000));
2843
2844 // unblock transmissions on the non-EMLSR link once the two packets are queued
2845 Simulator::ScheduleNow([=, this]() {
2846 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2847 m_apMac->GetAddress(),
2848 {*m_nonEmlsrLink});
2849 });
2850 }
2851
2852 // check that other EMLSR links are now blocked on the EMLSR client and on the AP MLD
2853 // after this QoS data frame is received
2854 Simulator::ScheduleNow([=, this]() {
2855 auto phyHdrTxTime = WifiPhy::CalculatePhyPreambleAndHeaderDuration(txVector);
2856 auto macHdrSize = (*psduMap.at(SU_STA_ID)->begin())->GetHeader().GetSerializedSize() +
2857 4 /* A-MPDU subframe header size */;
2858 auto macHdrTxTime =
2859 DataRate(txVector.GetMode().GetDataRate(txVector)).CalculateBytesTxTime(macHdrSize);
2860
2861 for (auto id : m_staMacs[0]->GetLinkIds())
2862 {
2864 m_staMacs[0],
2865 m_apMac->GetAddress(),
2866 id,
2867 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2868 id != m_staMacs[0]->GetLinkForPhy(m_mainPhyId) && m_staMacs[0]->IsEmlsrLink(id),
2869 "Checking EMLSR links on EMLSR client while sending the first data frame",
2870 false);
2871
2872 Simulator::Schedule(phyHdrTxTime + macHdrTxTime + MicroSeconds(1), [=, this]() {
2874 m_staMacs[0]->GetAddress(),
2875 id,
2876 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2877 id != m_staMacs[0]->GetLinkForPhy(m_mainPhyId) &&
2878 m_staMacs[0]->IsEmlsrLink(id),
2879 "Checking EMLSR links on AP MLD right after receiving the MAC "
2880 "header of the first data frame");
2881 });
2882
2885 [=, this]() {
2887 m_apMac,
2888 m_staMacs[0]->GetAddress(),
2889 id,
2890 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2891 id != m_staMacs[0]->GetLinkForPhy(m_mainPhyId) &&
2892 m_staMacs[0]->IsEmlsrLink(id),
2893 "Checking EMLSR links on AP MLD after sending the first data frame");
2894 });
2895 }
2896 });
2897
2898 if (m_nonEmlsrLink)
2899 {
2900 break;
2901 }
2902 m_countQoSframes++; // if all EMLSR links, next case is already executed now
2903 [[fallthrough]];
2904 case 4:
2905 // check that other EMLSR links are now blocked on the EMLSR client and on the AP MLD
2906 // after this QoS data frame is received
2907 Simulator::ScheduleNow([=, this]() {
2908 // make aux PHYs capable of transmitting frames
2909 auto auxPhyLinks = m_staMacs[0]->GetSetupLinkIds();
2910 auxPhyLinks.erase(m_mainPhyId);
2911 if (m_nonEmlsrLink)
2912 {
2913 auxPhyLinks.erase(*m_nonEmlsrLink);
2914 }
2915 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2916 m_apMac->GetAddress(),
2917 auxPhyLinks);
2918
2919 // block transmissions on the link where the main PHY is operating
2920 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2921 m_apMac->GetAddress(),
2922 {*m_staMacs[0]->GetLinkForPhy(m_mainPhyId)});
2923
2924 // generate data packets for another UL data frame, which will be sent on a link on
2925 // which an aux PHY is operating
2926 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
2927 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
2928 GetApplication(UPLINK, 0, 2, 1000));
2929 });
2930 break;
2931 case 5:
2932 // check that other EMLSR links are now blocked on both the EMLSR client and the AP MLD
2933 Simulator::ScheduleNow([=, this]() {
2934 for (auto id : m_staMacs[0]->GetLinkIds())
2935 {
2937 m_staMacs[0],
2938 m_apMac->GetAddress(),
2939 id,
2940 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2941 id != linkId && m_staMacs[0]->IsEmlsrLink(id),
2942 "Checking EMLSR links on EMLSR client while sending the second data frame",
2943 false);
2944
2946 m_apMac,
2947 m_staMacs[0]->GetAddress(),
2948 id,
2949 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2950 id != linkId && m_staMacs[0]->IsEmlsrLink(id),
2951 "Checking EMLSR links on AP MLD while sending the second data frame",
2952 false);
2953 }
2954
2955 // unblock transmission on the link where the main PHY is operating
2956 m_staMacs[0]->GetMacQueueScheduler()->UnblockQueues(
2957 WifiQueueBlockedReason::TID_NOT_MAPPED,
2958 AC_BE,
2960 m_apMac->GetAddress(),
2961 m_staMacs[0]->GetAddress(),
2962 {0},
2963 {m_mainPhyId});
2964 });
2965 break;
2966 }
2967}
2968
2969void
2971 const WifiTxVector& txVector,
2972 uint8_t linkId)
2973{
2975
2976 auto auxPhyLinks = m_staMacs[0]->GetSetupLinkIds();
2977 auxPhyLinks.erase(m_mainPhyId);
2978 if (m_nonEmlsrLink)
2979 {
2980 auxPhyLinks.erase(*m_nonEmlsrLink);
2981 }
2982
2983 auto txDuration =
2984 WifiPhy::CalculateTxDuration(psduMap, txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2985
2986 // in this test, BlockAck frames terminates TXOP, thus aux PHYs shall be in sleep mode before
2987 // the end of BlockAck reception and awake right afterwards
2988 if (linkId != m_nonEmlsrLink)
2989 {
2990 Simulator::Schedule(txDuration - TimeStep(1),
2992 this,
2993 m_staMacs[0],
2994 true);
2995 Simulator::Schedule(txDuration + TimeStep(1),
2997 this,
2998 m_staMacs[0],
2999 false);
3000
3001 // if the TXOP has been carried out on a link other than the preferred link, the main PHY
3002 // switches back to the preferred link when the TXOP ends
3003 if (m_staMacs[0]->GetLinkForPhy(m_mainPhyId) != linkId)
3004 {
3005 Simulator::Schedule(txDuration + TimeStep(1), [=, this]() {
3006 // check the traced remaining time before calling CheckMainPhyTraceInfo
3007 if (const auto traceInfoIt = m_traceInfo.find(0);
3008 traceInfoIt != m_traceInfo.cend() &&
3009 traceInfoIt->second->GetName() == "TxopEnded")
3010 {
3011 const auto& traceInfo =
3012 static_cast<const EmlsrTxopEndedTrace&>(*traceInfoIt->second);
3014 traceInfo.remTime,
3015 Time{0},
3016 "Expected null remaining time because TXOP ended regularly");
3017 }
3018
3019 CheckMainPhyTraceInfo(0, "TxopEnded", linkId, m_mainPhyId);
3020 });
3021 }
3022 }
3023
3024 switch (m_countBlockAck)
3025 {
3026 case 1:
3027 case 2:
3028 // do nothing, these are BlockAcks in response to the QoS data frames sent to establish
3029 // BA agreements in DL and UL direction
3030 break;
3031 case 3:
3032 if (linkId == m_nonEmlsrLink)
3033 {
3034 // this BlockAck has been sent on the non-EMLSR link, ignore it
3035 break;
3036 }
3037 m_checkBackoffStarted = true;
3038 if (!m_nonEmlsrLink)
3039 {
3040 m_countBlockAck++; // if all EMLSR links, next case is already executed now
3041 }
3042 [[fallthrough]];
3043 case 4:
3044 if (m_nonEmlsrLink && m_countBlockAck == 4)
3045 {
3046 // block transmissions on the non-EMLSR link
3047 Simulator::Schedule(txDuration + NanoSeconds(1), [=, this]() {
3048 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
3049 m_apMac->GetAddress(),
3050 {*m_nonEmlsrLink});
3051 });
3052 }
3053 if (linkId == m_nonEmlsrLink)
3054 {
3055 // this BlockAck has been sent on the non-EMLSR link, ignore it
3056 break;
3057 }
3058 m_checkBackoffStarted = true;
3059 break;
3060 case 5:
3061 // Block Ack in response to the second data frame sent by the EMLSR client on EMLSR links.
3062 // Check that MediumSyncDelay timer starts running on the link where the main PHY switches
3063 // to when the channel switch is completed
3065 txDuration + m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId)->GetChannelSwitchDelay() +
3066 NanoSeconds(1),
3067 [=, this]() {
3068 auto elapsed =
3069 m_staMacs[0]->GetEmlsrManager()->GetElapsedMediumSyncDelayTimer(m_mainPhyId);
3071 elapsed.has_value(),
3072 true,
3073 "MediumSyncDelay timer not running on link where main PHY is operating");
3075 m_staMacs[0]->GetEmlsrManager()->GetMediumSyncDuration() -
3076 *elapsed;
3077 });
3078
3079 // Check that the number of backoff slots is not changed since the beginning of the TXOP
3080 Simulator::Schedule(txDuration, [=, this]() {
3081 m_checkBackoffStarted = false;
3083 true,
3084 "Backoff end time should have been calculated");
3085 // when this BlockAck is received, the TXOP ends and the main PHY link is unblocked,
3086 // which causes a new backoff timer to be generated if the backoff timer is not
3087 // already running
3089 });
3090
3091 // make aux PHYs not capable of transmitting frames
3092 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
3093 m_apMac->GetAddress(),
3094 auxPhyLinks);
3095
3096 // generate data packets for another UL data frame, which will be sent on the link where
3097 // the main PHY is operating
3098 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
3099 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
3100 break;
3101 case 6: {
3102 // block transmission on the main PHY link and on the non-EMLSR link (if any), so that
3103 // the next QoS frames are sent on a link where an aux PHY is operating
3104 std::set<uint8_t> linkIds{m_mainPhyId};
3105 if (m_nonEmlsrLink)
3106 {
3107 linkIds.insert(*m_nonEmlsrLink);
3108 }
3109 m_staMacs[0]->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
3110 AC_BE,
3112 m_apMac->GetAddress(),
3113 m_staMacs[0]->GetAddress(),
3114 {0},
3115 linkIds);
3116 }
3117 // make sure aux PHYs are capable of transmitting frames
3118 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
3119 m_apMac->GetAddress(),
3120 auxPhyLinks);
3121
3122 // generate data packets for another UL data frame
3123 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
3124 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
3125 break;
3126 case 7:
3127 // make the aux PHY(s) not capable of transmitting frames
3128 m_staMacs[0]->GetEmlsrManager()->SetAuxPhyTxCapable(false);
3129 if (!m_nonEmlsrLink)
3130 {
3131 // if there are two auxiliary links, set MediumSyncDuration to zero so that the
3132 // next UL QoS data frame is not protected also in case it is transmitted on the
3133 // auxiliary link other than the one on which the last frame exchange occurred
3134 m_staMacs[0]->GetEmlsrManager()->SetMediumSyncDuration(Seconds(0));
3135 }
3136
3137 // generate a very large backoff for the preferred link, so that when an aux PHY gains a
3138 // TXOP, it requests the main PHY to switch to its link to transmit the frames
3139 m_staMacs[0]->GetQosTxop(AC_BE)->StartBackoffNow(100, m_mainPhyId);
3140
3141 // events to be scheduled at the end of the BlockAck response
3142 Simulator::Schedule(txDuration + NanoSeconds(1), [=, this]() {
3143 // check that the main PHY switches to its preferred link
3144 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
3145
3146 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
3147 true,
3148 "Main PHY is not switching at time "
3149 << Simulator::Now().As(Time::NS));
3150
3151 // events to be scheduled when the first main PHY channel switch is completed
3152 Simulator::Schedule(mainPhy->GetChannelSwitchDelay(), [=, this]() {
3153 // either the main PHY is operating on the preferred link or it is switching again
3154 auto mainPhyLinkid = m_staMacs[0]->GetLinkForPhy(mainPhy);
3155 if (mainPhyLinkid)
3156 {
3157 NS_TEST_EXPECT_MSG_EQ(+mainPhyLinkid.value(),
3158 +m_mainPhyId,
3159 "Main PHY expected to operate on the preferred link");
3160 }
3161 else
3162 {
3163 NS_TEST_EXPECT_MSG_EQ(
3164 mainPhy->IsStateSwitching(),
3165 true,
3166 "Main PHY is not operating on a link and it is not switching at time "
3167 << Simulator::Now().As(Time::NS));
3168 }
3169
3170 auto acBe = m_staMacs[0]->GetQosTxop(AC_BE);
3171
3172 // find the min remaining backoff time on auxiliary links for AC BE
3173 auto minBackoff = Time::Max();
3174 Time slot{0};
3175 for (uint8_t id = 0; id < m_staMacs[0]->GetNLinks(); id++)
3176 {
3177 if (!m_staMacs[0]->GetWifiPhy(id))
3178 {
3179 continue; // no PHY on this link
3180 }
3181
3182 if (auto backoff =
3183 m_staMacs[0]->GetChannelAccessManager(id)->GetBackoffEndFor(acBe);
3184 id != m_mainPhyId && m_staMacs[0]->IsEmlsrLink(id) && backoff < minBackoff)
3185 {
3186 minBackoff = backoff;
3187 slot = m_staMacs[0]->GetWifiPhy(id)->GetSlot();
3188 }
3189 }
3190
3191 // if the backoff on a link has expired before the end of the main PHY channel
3192 // switch, the main PHY will be requested to switch again no later than the first
3193 // slot boundary after the end of the channel switch. Otherwise, it will be
3194 // requested to switch when the backoff expires or when the backoff counter reaches
3195 // the configured number of slots
3196 auto expected2ndSwitchDelay =
3197 (minBackoff <= Simulator::Now()) ? mainPhy->GetSlot()
3198 : m_nSlotsLeftAlert > 0
3199 ? Max(minBackoff - m_nSlotsLeftAlert * slot - Simulator::Now(), Time{0})
3200 : (minBackoff - Simulator::Now());
3201
3202 // check that the main PHY is requested to switch to an auxiliary link after
3203 // the expected delay
3204 Simulator::Schedule(expected2ndSwitchDelay + NanoSeconds(1), [=, this]() {
3205 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
3206 true,
3207 "Main PHY is not switching at time "
3208 << Simulator::Now().As(Time::NS));
3209 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetLinkForPhy(mainPhy).has_value(),
3210 false,
3211 "Main PHY should not be operating on a link because it "
3212 "should be switching to an auxiliary link");
3213 // check that the appropriate trace info was received
3215 "UlTxopAuxPhyNotTxCapable",
3216 std::nullopt,
3217 0,
3218 false,
3219 false);
3220
3222 {
3223 TimeValue switchMainPhyBackDelay;
3224 m_staMacs[0]->GetEmlsrManager()->GetAttribute("SwitchMainPhyBackDelay",
3225 switchMainPhyBackDelay);
3226
3227 // set the NAV on all the links for enough time to make the
3228 // SwitchMainPhyBackDelay timer expire
3229 for (uint8_t id = 0; id < m_staMacs[0]->GetNLinks(); ++id)
3230 {
3231 if (!m_staMacs[0]->GetWifiPhy(id))
3232 {
3233 continue; // no PHY on this link
3234 }
3235 m_staMacs[0]->GetChannelAccessManager(id)->NotifyNavStartNow(
3236 2 * switchMainPhyBackDelay.Get());
3237 }
3238 // check that the SwitchMainPhyBackDelay timer expires
3239 Simulator::Schedule(2 * switchMainPhyBackDelay.Get() - mainPhy->GetPifs(),
3240 [=, this]() {
3241 CheckMainPhyTraceInfo(0,
3242 "TxopNotGainedOnAuxPhyLink",
3243 std::nullopt,
3244 m_mainPhyId,
3245 false);
3246 });
3247 }
3248
3249 // events to be scheduled when main PHY finishes switching to auxiliary link
3250 Simulator::Schedule(mainPhy->GetDelayUntilIdle(), [=, this]() {
3251 auto nonPrimLinkId = m_staMacs[0]->GetLinkForPhy(mainPhy);
3252
3253 NS_TEST_ASSERT_MSG_EQ(nonPrimLinkId.has_value(),
3254 true,
3255 "Main PHY should have completed switching");
3256 // update backoff on the auxiliary link on which main PHY is operating
3257 m_staMacs[0]
3258 ->GetChannelAccessManager(*nonPrimLinkId)
3259 ->NeedBackoffUponAccess(acBe, true, true);
3260 // record the time the transmission of the QoS data frames must have
3261 // started: (a PIFS after) end of channel switch, if the backoff counter
3262 // on the auxiliary link is null and UseAuxPhyCca is true (false); when
3263 // the backoff expires, otherwise
3264 if (auto slots = acBe->GetBackoffSlots(*nonPrimLinkId); slots == 0)
3265 {
3266 m_5thQosFrameTxTime =
3267 Simulator::Now() + (m_useAuxPhyCca ? Time{0} : mainPhy->GetPifs());
3268 }
3269 else
3270 {
3271 m_5thQosFrameTxTime = m_staMacs[0]
3272 ->GetChannelAccessManager(*nonPrimLinkId)
3273 ->GetBackoffEndFor(acBe);
3274 }
3275 });
3276 });
3277 });
3278 });
3279
3280 // generate data packets for another UL data frame
3281 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
3282 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
3283 break;
3284 }
3285}
3286
3287void
3289 const WifiTxVector& txVector,
3290 uint8_t linkId)
3291{
3293 {
3294 // this function only considers RTS frames sent after the first QoS data frame
3295 return;
3296 }
3297
3298 if (linkId != m_mainPhyId)
3299 {
3300 if (m_countRtsframes > 0 && !m_corruptCts.has_value())
3301 {
3302 // we get here for the frame exchange in which the CTS response must be corrupted.
3303 // Install post reception error model on the STA affiliated with the EMLSR client that
3304 // is transmitting this RTS frame
3306 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
3307 m_corruptCts = true;
3308 }
3309
3310 return;
3311 }
3312
3313 // we get here for RTS frames sent by the main PHY while the MediumSyncDelay timer is running
3315
3317 m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId)->GetChannelWidth(),
3318 "RTS sent by main PHY on an unexpected width");
3319
3320 // corrupt reception at AP MLD
3321 NS_LOG_INFO("CORRUPTED");
3322 m_errorModel->SetList({mpdu->GetPacket()->GetUid()});
3323}
3324
3325void
3327 const WifiTxVector& txVector,
3328 uint8_t linkId)
3329{
3331 {
3332 // this function only considers CTS frames sent after the first QoS data frame
3333 return;
3334 }
3335
3336 auto txDuration = WifiPhy::CalculateTxDuration(mpdu->GetSize(),
3337 txVector,
3338 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
3339 const auto doCorruptCts = m_corruptCts.has_value() && *m_corruptCts;
3340
3341 if (linkId != m_staMacs[0]->GetLinkForPhy(m_mainPhyId) && linkId != m_nonEmlsrLink &&
3342 mpdu->GetHeader().GetAddr1() == m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress())
3343 {
3344 // this is a CTS sent to an aux PHY starting an UL TXOP. Given that aux PHYs do not
3345 // switch channel, they are put in sleep mode when the main PHY starts operating on their
3346 // link, which coincides with the end of CTS plus two propagation delays
3347 const auto auxPhy = m_staMacs[0]->GetWifiPhy(linkId);
3348 const auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
3349 Simulator::Schedule(txDuration, [=, this]() {
3350 // when CTS ends, the main PHY is still switching and the aux PHY is not yet sleeping
3351 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
3352 true,
3353 "Expecting the main PHY to be switching link");
3354 NS_TEST_EXPECT_MSG_EQ(auxPhy->IsStateSleep(),
3355 false,
3356 "Aux PHY on link " << +linkId << " already in sleep mode");
3357 // when CTS is sent, the main PHY may have already started switching, thus we may not
3358 // know which link the main PHY is moving from
3359 CheckMainPhyTraceInfo(0, "UlTxopRtsSentByAuxPhy", std::nullopt, linkId, false);
3360 });
3362 txDuration + MicroSeconds(2 * MAX_PROPAGATION_DELAY_USEC) + TimeStep(1),
3363 [=, this]() {
3364 // aux PHYs are put to sleep if and only if CTS is not corrupted
3365 // (causing the end of the TXOP)
3366 CheckAuxPhysSleepMode(m_staMacs[0], !doCorruptCts);
3367 // if CTS is corrupted, TXOP ends and the main PHY switches back
3368 // to the preferred link
3369 if (doCorruptCts)
3370 {
3371 // check the traced remaining time before calling CheckMainPhyTraceInfo
3372 if (const auto traceInfoIt = m_traceInfo.find(0);
3373 traceInfoIt != m_traceInfo.cend() &&
3374 traceInfoIt->second->GetName() == "TxopEnded")
3375 {
3376 const auto& traceInfo =
3377 static_cast<const EmlsrTxopEndedTrace&>(*traceInfoIt->second);
3378 NS_TEST_EXPECT_MSG_GT(traceInfo.remTime,
3379 Time{0},
3380 "Expected non-zero remaining time because main PHY "
3381 "was switching when TXOP ended");
3382 }
3383
3384 CheckMainPhyTraceInfo(0, "TxopEnded", linkId, m_mainPhyId);
3385 }
3386 });
3387 }
3388
3389 if (doCorruptCts)
3390 {
3391 // corrupt reception at EMLSR client
3392 NS_LOG_INFO("CORRUPTED");
3393 m_errorModel->SetList({mpdu->GetPacket()->GetUid()});
3394 m_corruptCts = false;
3395 }
3396}
3397
3398void
3408
3409void
3411{
3412 if (m_msdMaxNTxops > 0)
3413 {
3417 "Unexpected number of RTS frames sent while the MediumSyncDelay timer is running");
3418 }
3419
3420 auto psduIt = m_txPsdus.cbegin();
3421
3422 // lambda to jump to the next QoS data frame or MU-RTS Trigger Frame or RTS transmitted
3423 // to/by an EMLSR client
3424 auto jumpToQosDataOrMuRts = [&]() {
3425 while (psduIt != m_txPsdus.cend() &&
3426 !psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData() &&
3427 !psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts())
3428 {
3429 auto psdu = psduIt->psduMap.cbegin()->second;
3430 if (psdu->GetHeader(0).IsTrigger())
3431 {
3432 CtrlTriggerHeader trigger;
3433 psdu->GetPayload(0)->PeekHeader(trigger);
3434 if (trigger.IsMuRts())
3435 {
3436 break;
3437 }
3438 }
3439 psduIt++;
3440 }
3441 };
3442
3443 /**
3444 * EMLSR client with EMLSR mode enabled on all links (main PHY ID = 1).
3445 *
3446 * main PHY│
3447 * blocked,│
3448 * aux PHYs││main PHY blocked│
3449 * cannot │
3450 * transmit│
3451 * │ ┌───┐ ┌──┐
3452 * [link 0] │CTS│ │BA│
3453 * ────────────────────────┬───┬┴───┴┬───┬───┬┴──┴─────────────────────────────────────────
3454 * │RTS│ │QoS│QoS│
3455 * └───┘ │ 6 │ 7 │
3456 * └───┴───┘
3457 * gen backoff gen backoff if MediumSyncDelay
3458 * ┌──┐ (also many times) not running timer expired ┌──┐
3459 * [link 1] │BA│ │ if allowed │ │ │BA│
3460 * ─────────┬───┬───┬┴──┴───────────────────────────┬───┬─────┬───┬────┬───┬───┬┴──┴───────
3461 * │QoS│QoS│ │RTS│ ... │RTS│ │QoS│QoS│
3462 * │ 4 │ 5 │ └───┘ └───┘ │ 8 │ 9 │
3463 * └───┴───┘ └───┴───┘
3464 *
3465 * [link 2]
3466 * ───────────────────────────────────────────────────────────────────────────
3467 *
3468 *
3469 *
3470 * EMLSR client with EMLSR mode enabled on links 0 and 1 (main PHY ID = 1).
3471 *
3472 * main PHY │
3473 * and │
3474 * non-EMLSR│
3475 * link │
3476 * blocked,│
3477 * aux PHYs││main PHY blocked│
3478 * cannot │
3479 * transmit│
3480 * │ ┌───┐ ┌──┐
3481 * [link 0] │CTS│ │BA│
3482 * ────────────────────────┬───┬┴───┴┬───┬───┬┴──┴─────────────────────────────────────────
3483 * │RTS│ │QoS│QoS│
3484 * └───┘ │ 8 │ 9 │
3485 * └───┴───┘
3486 * gen backoff gen backoff if MediumSyncDelay
3487 * ┌──┐ (also many times) not running timer expired ┌──┐
3488 * [link 1] │BA│ │ if allowed │ │ │BA│
3489 * ─────────┬───┬───┬┴──┴───────────────────────────┬───┬─────┬───┬────┬───┬───┬┴──┴───────
3490 * │QoS│QoS│ │RTS│ ... │RTS│ │QoS│QoS│
3491 * │ 4 │ 5 │ └───┘ └───┘ │ 10│ 11│
3492 * └───┴───┘ └───┴───┘
3493 * ┌──┐
3494 * [link 2] │BA│
3495 * ──────────┬───┬───┬┴──┴────────────────────────────────────────────────────────────
3496 * │QoS│QoS│
3497 * │ 6 │ 7 │
3498 * └───┴───┘
3499 *
3500 * For both scenarios, after the last frame exchange on the main PHY link, we have the
3501 * following frame exchanges on an EMLSR link where an aux PHY is operating on. After the
3502 * first frame exchange, aux PHYs are configured as non-TX capable. Note that the two frame
3503 * exchanges may occur on distinct auxiliary EMLSR links.
3504 *
3505 * | main PHY || main PHY |
3506 * [ link ] ┌───┐ ┌───┐ ┌──┐|switches to||switches to| ┌──┐
3507 * [0 or 2] │CTS│ │CTS│ │BA│| preferred ||auxiliary |PIFS| │BA│
3508 * ──────┬───┬┴───X────┬───┬┴───┴┬───┬───┬┴──┴──────────────────────────────┬───┬───┬┴──┴───
3509 * │RTS│ │RTS│ │QoS│QoS│ │QoS│QoS│
3510 * └───┘ └───┘ │ X │ Y │ │ Z │ W │
3511 * └───┴───┘ └───┴───┘
3512 * For all EMLSR links scenario, X=10, Y=11, Z=12, W=13
3513 * For the scenario with a non-EMLSR link, X=12, Y=13, Z=14, W=15
3514 */
3515
3516 // jump to the first (non-Beacon) frame transmitted after establishing BA agreements and
3517 // enabling EMLSR mode
3518 while (psduIt != m_txPsdus.cend() &&
3519 (psduIt->startTx < m_firstUlPktsGenTime ||
3520 psduIt->psduMap.cbegin()->second->GetHeader(0).IsBeacon()))
3521 {
3522 ++psduIt;
3523 }
3524
3525 // the first QoS data frame is transmitted by the main PHY without RTS protection as soon
3526 // as transmissions on the link where the main PHY is operating are unblocked (at this
3527 // moment, aux PHYs cannot transmit)
3528 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3529 true,
3530 "First QoS data frame has not been transmitted");
3531 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
3532 true,
3533 "First QoS data frame should be transmitted without protection");
3534 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
3535 +m_mainPhyId,
3536 "First QoS data frame should be transmitted by the main PHY");
3537 NS_TEST_EXPECT_MSG_GT_OR_EQ(psduIt->startTx,
3539 "First QoS data frame sent too early");
3540
3541 auto prevPsduIt = psduIt++;
3542 jumpToQosDataOrMuRts();
3543
3544 if (m_nonEmlsrLink)
3545 {
3546 // an additional data frame is sent concurrently on the non-EMLSR link
3548 (psduIt != m_txPsdus.cend()),
3549 true,
3550 "Expected another QoS data frame sent concurrently with the first frame");
3552 psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
3553 true,
3554 "First data frame on non-EMLSR link should be transmitted without protection");
3555 NS_TEST_EXPECT_MSG_EQ(+psduIt->linkId,
3556 +m_nonEmlsrLink.value(),
3557 "First data frame expected to be transmitted on the non-EMLSR link");
3558 const auto txDuration =
3559 WifiPhy::CalculateTxDuration(prevPsduIt->psduMap,
3560 prevPsduIt->txVector,
3561 m_staMacs[0]->GetWifiPhy(prevPsduIt->phyId)->GetPhyBand());
3562 NS_TEST_EXPECT_MSG_LT(psduIt->startTx,
3563 prevPsduIt->startTx + txDuration,
3564 "First data frame on the non-EMLSR link not sent concurrently");
3565 psduIt++;
3566 jumpToQosDataOrMuRts();
3567 }
3568
3569 // the second QoS data frame is transmitted by the main PHY after that the aux PHY has
3570 // obtained a TXOP and sent an RTS
3571 // RTS
3572 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3573 true,
3574 "RTS before second QoS data frame has not been transmitted");
3575 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(),
3576 true,
3577 "Second QoS data frame should be transmitted with protection");
3579 +psduIt->phyId,
3580 +m_mainPhyId,
3581 "RTS before second QoS data frame should not be transmitted by the main PHY");
3582 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3584 "RTS before second data frame transmitted on an unexpected width");
3585 psduIt++;
3586 // CTS
3587 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3588 true,
3589 "CTS before second QoS data frame has not been transmitted");
3590 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(),
3591 true,
3592 "CTS before second QoS data frame has not been transmitted");
3593 psduIt++;
3594 // QoS Data
3595 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3596 true,
3597 "Second QoS data frame has not been transmitted");
3598 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
3599 true,
3600 "Second QoS data frame has not been transmitted");
3601 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
3602 +m_mainPhyId,
3603 "Second QoS data frame should be transmitted by the main PHY");
3604 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3606 "Second data frame not transmitted on the same width as RTS");
3607
3608 bool moreQosDataFound = false;
3609
3610 while (++psduIt != m_txPsdus.cend())
3611 {
3612 jumpToQosDataOrMuRts();
3613 if (psduIt != m_txPsdus.cend() &&
3614 psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData())
3615 {
3616 moreQosDataFound = true;
3617
3618 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
3619 +m_mainPhyId,
3620 "Third QoS data frame should be transmitted by the main PHY");
3621 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3622 m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId)->GetChannelWidth(),
3623 "Expecting TX width of third data frame to equal the channel "
3624 "width used by the main PHY");
3626 psduIt->startTx,
3628 "Third QoS data frame sent before MediumSyncDelay timer expired");
3629
3630 break;
3631 }
3632 }
3633
3634 NS_TEST_EXPECT_MSG_EQ(moreQosDataFound,
3635 true,
3636 "Third QoS data frame transmitted by the main PHY not found");
3637
3638 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()), true, "Expected more frames");
3639 ++psduIt;
3640 jumpToQosDataOrMuRts();
3641
3642 // the first attempt at transmitting the fourth QoS data frame fails because CTS is corrupted
3643 // RTS
3644 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3645 true,
3646 "RTS before fourth QoS data frame has not been transmitted");
3647 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(),
3648 true,
3649 "Fourth QoS data frame should be transmitted with protection");
3651 +psduIt->phyId,
3652 +m_mainPhyId,
3653 "RTS before fourth QoS data frame should not be transmitted by the main PHY");
3654 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3656 "RTS before fourth data frame transmitted on an unexpected width");
3657 psduIt++;
3658 // CTS
3659 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3660 true,
3661 "CTS before fourth QoS data frame has not been transmitted");
3662 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(),
3663 true,
3664 "CTS before fourth QoS data frame has not been transmitted");
3665 psduIt++;
3666 jumpToQosDataOrMuRts();
3667
3668 // the fourth QoS data frame is transmitted by an aux PHY after that the aux PHY has
3669 // obtained a TXOP and sent an RTS
3670 // RTS
3671 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3672 true,
3673 "RTS before fourth QoS data frame has not been transmitted");
3674 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(),
3675 true,
3676 "Fourth QoS data frame should be transmitted with protection");
3678 +psduIt->phyId,
3679 +m_mainPhyId,
3680 "RTS before fourth QoS data frame should not be transmitted by the main PHY");
3681 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3683 "RTS before fourth data frame transmitted on an unexpected width");
3684 psduIt++;
3685 // CTS
3686 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3687 true,
3688 "CTS before fourth QoS data frame has not been transmitted");
3689 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(),
3690 true,
3691 "CTS before fourth QoS data frame has not been transmitted");
3692 psduIt++;
3693 // QoS Data
3694 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3695 true,
3696 "Fourth QoS data frame has not been transmitted");
3697 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
3698 true,
3699 "Fourth QoS data frame has not been transmitted");
3700 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
3701 +m_mainPhyId,
3702 "Fourth QoS data frame should be transmitted by the main PHY");
3703 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3705 "Fourth data frame not transmitted on the same width as RTS");
3706
3707 auto fourthLinkId = psduIt->linkId;
3708
3709 psduIt++;
3710 jumpToQosDataOrMuRts();
3711
3712 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()), true, "Expected more frames");
3713 // Do not check the start transmission time if a backoff is generated even when no
3714 // transmission is done (if the backoff expires while the main PHY is switching, a new
3715 // backoff is generated and, before this backoff expires, the main PHY may be requested
3716 // to switch to another auxiliary link; this may happen multiple times...)
3718 {
3719 NS_TEST_EXPECT_MSG_LT_OR_EQ(psduIt->startTx,
3721 "Fifth data frame transmitted too late");
3722 }
3724 psduIt->txVector.GetChannelWidth(),
3726 "Fifth data frame not transmitted on the correct channel width");
3727
3728 // the fifth QoS data frame is transmitted by the main PHY on an auxiliary link because
3729 // the aux PHY is not TX capable. The QoS data frame is protected by RTS if it is transmitted
3730 // on a different link than the previous one (because the MediumSyncDelay timer is running)
3731 if (psduIt->linkId != fourthLinkId)
3732 {
3733 // RTS
3734 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(),
3735 true,
3736 "Fifth QoS data frame should be transmitted with protection");
3738 +psduIt->phyId,
3739 +m_mainPhyId,
3740 "RTS before fifth QoS data frame should be transmitted by the main PHY");
3741 psduIt++;
3742 // CTS
3743 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3744 true,
3745 "CTS before fifth QoS data frame has not been transmitted");
3746 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(),
3747 true,
3748 "CTS before fifth QoS data frame has not been transmitted");
3749 psduIt++;
3750 }
3751
3752 // QoS Data
3753 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3754 true,
3755 "Fifth QoS data frame has not been transmitted");
3756 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
3757 true,
3758 "Fifth QoS data frame has not been transmitted");
3759 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
3760 +m_mainPhyId,
3761 "Fifth QoS data frame should be transmitted by the main PHY");
3762 NS_TEST_EXPECT_MSG_NE(+psduIt->linkId,
3763 +m_mainPhyId,
3764 "Fifth QoS data frame should be transmitted on an auxiliary link");
3766 psduIt->txVector.GetChannelWidth(),
3768 "Fifth data frame not transmitted on the correct channel width");
3769}
3770
3772 : EmlsrOperationsTestBase("Check UL OFDMA operations with an EMLSR client"),
3773 m_enableBsrp(enableBsrp),
3774 m_txPsdusPos(0),
3775 m_startAccessReq(0)
3776{
3777 m_linksToEnableEmlsrOn = {0, 1, 2};
3778 m_nEmlsrStations = 1;
3780 m_establishBaDl = false;
3781 m_establishBaUl = true;
3782 m_mainPhyId = 1;
3783 m_duration = Seconds(1);
3784}
3785
3786void
3788{
3789 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(m_transitionDelay.at(0)));
3790
3792
3793 m_apMac->GetQosTxop(AC_BE)->SetTxopLimits(
3794 {MicroSeconds(3200), MicroSeconds(3200), MicroSeconds(3200)});
3795
3796 auto muScheduler = CreateObjectWithAttributes<RrMultiUserScheduler>("EnableUlOfdma",
3797 BooleanValue(true),
3798 "EnableBsrp",
3800 m_apMac->AggregateObject(muScheduler);
3801}
3802
3803void
3805 uint8_t phyId,
3806 WifiConstPsduMap psduMap,
3807 WifiTxVector txVector,
3808 double txPowerW)
3809{
3810 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
3811 auto linkId = m_txPsdus.back().linkId;
3812
3813 auto psdu = psduMap.begin()->second;
3814
3815 switch (psdu->GetHeader(0).GetType())
3816 {
3819 {
3820 // this is the first Trigger Frame sent after the AP requested channel access
3821 // through the Multi-user scheduler and it is an ICF for the EMLSR client
3822 m_txPsdusPos = m_txPsdus.size() - 1;
3823 auto txDuration = WifiPhy::CalculateTxDuration(psduMap,
3824 txVector,
3825 mac->GetWifiPhy(linkId)->GetPhyBand());
3826 NS_LOG_INFO("This is the first Trigger Frame\n");
3827 // once the Trigger Frame is received by the EMLSR client, make the client application
3828 // on the EMLSR client generate two packets. These packets will be sent via UL OFDMA
3829 // because the EMLSR client has blocked transmissions on other links when receiving
3830 // this Trigger Frame, hence it will not try to get access on other links via EDCA
3832 txDuration + MicroSeconds(1), // to account for propagation delay
3833 [=, this]() {
3834 for (const auto id : m_staMacs[0]->GetLinkIds())
3835 {
3837 m_staMacs[0]->GetFrameExchangeManager(id));
3839 ehtFem->UsingOtherEmlsrLink(),
3840 (id != linkId),
3841 "Link " << +id << " was" << (id == linkId ? " not" : "")
3842 << " expected to be blocked on EMLSR client at time "
3843 << Simulator::Now().As(Time::NS));
3844 }
3845 NS_LOG_INFO("Generate two packets\n");
3846 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
3847 GetApplication(UPLINK, 0, 2, 100));
3848 });
3849 }
3850 break;
3851
3854 {
3855 CtrlBAckResponseHeader blockAck;
3856 psdu->GetPayload(0)->PeekHeader(blockAck);
3857 if (blockAck.IsMultiSta())
3858 {
3859 auto txDuration =
3861 txVector,
3862 mac->GetWifiPhy(linkId)->GetPhyBand());
3863 Simulator::Stop(txDuration + MicroSeconds(1));
3864 }
3865 }
3866 break;
3867
3868 default:;
3869 }
3870
3871 if (psdu->GetHeader(0).IsCfEnd())
3872 {
3873 // we do not check CF-End frames
3874 m_txPsdus.pop_back();
3875 }
3876}
3877
3878void
3888
3889void
3891{
3892 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
3893 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "No MU scheduler installed on AP MLD");
3894
3895 NS_LOG_INFO("Setting Access Request interval");
3896
3897 const auto interval = MilliSeconds(50);
3898 muScheduler->SetAccessReqInterval(interval);
3899 m_startAccessReq = Simulator::Now() + interval;
3900}
3901
3902void
3904{
3905 /**
3906 * Sending BSRP TF disabled.
3907 *
3908 * The figure assumes that link 0 is used to send the first Trigger Frame after that the
3909 * AP MLD requests channel access through the Multi-user scheduler. The first Trigger Frame
3910 * is MU-RTS because EMLSR client needs an ICF; the other Trigger Frames are Basic TFs and
3911 * do not solicit the EMLSR client.
3912 * ┌─────┐ ┌─────┐ ┌──────┐
3913 * │ MU │ │Basic│ │Multi-│
3914 * [link 0] │ RTS │ │ TF │ │STA BA│
3915 * ───────────┴─────┴┬───┬┴─────┴┬────────┬─┴──────┴───────────────
3916 * │CTS│ │QoS Null│
3917 * ├───┤ ├────────┤
3918 * │CTS│ │QoS Data│
3919 * └───┘ └────────┘
3920 *
3921 * ┌─────┐
3922 * │Basic│
3923 * [link 1] │ TF │
3924 * ─────────────┴─────┴┬────┬──────────────────────────────────────
3925 * │QoS │
3926 * │Null│
3927 * └────┘
3928 *
3929 * ┌─────┐
3930 * │Basic│
3931 * [link 2] │ TF │
3932 * ─────────────┴─────┴┬────┬──────────────────────────────────────
3933 * │QoS │
3934 * │Null│
3935 * └────┘
3936 *
3937 * Sending BSRP TF enabled.
3938 *
3939 * The figure assumes that link 0 is used to send the first Trigger Frame after that the
3940 * AP MLD requests channel access through the Multi-user scheduler. The first Trigger Frames
3941 * are all BSRP Trigger Frames, but only the first one solicits the EMLSR client, too.
3942 * ┌─────┐ ┌─────┐ ┌──────┐
3943 * │BSRP │ │Basic│ │Multi-│
3944 * [link 0] │ TF │ │ TF │ │STA BA│
3945 * ───────────┴─────┴┬────────┬┴─────┴┬────────┬─┴──────┴──────────
3946 * │QoS Null│ │QoS Data│
3947 * ├────────┤ └────────┘
3948 * │QoS Null│
3949 * └────────┘
3950 *
3951 * ┌─────┐
3952 * │BSRP │
3953 * [link 1] │ TF │
3954 * ─────────────┴─────┴┬────┬──────────────────────────────────────
3955 * │QoS │
3956 * │Null│
3957 * └────┘
3958 *
3959 * ┌─────┐
3960 * │BSRP │
3961 * [link 2] │ TF │
3962 * ─────────────┴─────┴┬────┬──────────────────────────────────────
3963 * │QoS │
3964 * │Null│
3965 * └────┘
3966 */
3967
3968 NS_TEST_ASSERT_MSG_GT(m_txPsdusPos, 0, "First Trigger Frame not detected");
3969
3970 // Check the Trigger Frames (one per link) after requesting channel access
3971 auto index = m_txPsdusPos;
3972 const auto firstLinkId = m_txPsdus[m_txPsdusPos].linkId;
3973 for (; index < m_txPsdusPos + 3; ++index)
3974 {
3975 NS_TEST_ASSERT_MSG_EQ(m_txPsdus[index].psduMap.cbegin()->second->GetHeader(0).IsTrigger(),
3976 true,
3977 "Expected a Trigger Frame");
3978 CtrlTriggerHeader trigger;
3979 m_txPsdus[index].psduMap.cbegin()->second->GetPayload(0)->PeekHeader(trigger);
3980
3981 TriggerFrameType triggerType =
3982 m_enableBsrp ? TriggerFrameType::BSRP_TRIGGER
3983 : (index == m_txPsdusPos ? TriggerFrameType::MU_RTS_TRIGGER
3984 : TriggerFrameType::BASIC_TRIGGER);
3985 NS_TEST_EXPECT_MSG_EQ(+static_cast<uint8_t>(trigger.GetType()),
3986 +static_cast<uint8_t>(triggerType),
3987 "Unexpected Trigger Frame type on link " << +m_txPsdus[index].linkId);
3988
3989 // only the first TF solicits the EMLSR client and the non-AP MLD
3991 trigger.GetNUserInfoFields(),
3992 (index == m_txPsdusPos ? 2 : 1),
3993 "Unexpected number of User Info fields for Trigger Frame, index=" << index);
3994 }
3995
3996 auto startIndex = index;
3997 std::size_t ctsCount = 0;
3998 std::size_t qosNullCount = 0;
3999 // Check responses to Trigger Frames
4000 for (; index < startIndex + 4; ++index)
4001 {
4002 const auto& hdr = m_txPsdus[index].psduMap.cbegin()->second->GetHeader(0);
4003
4004 if (hdr.IsCts())
4005 {
4006 ++ctsCount;
4007 continue;
4008 }
4009
4010 if (hdr.IsQosData() && !hdr.HasData())
4011 {
4012 ++qosNullCount;
4013 // if BSRP is enabled, the QoS Null frame sent by the EMLSR client in response to the
4014 // first BSRP TF reports a non-null buffer status
4015 if (m_enableBsrp &&
4016 hdr.GetAddr2() == m_staMacs[0]->GetFrameExchangeManager(firstLinkId)->GetAddress())
4017 {
4018 NS_TEST_EXPECT_MSG_GT(+hdr.GetQosQueueSize(), 0, "Unexpected buffer size");
4019 }
4020 else
4021 {
4022 NS_TEST_EXPECT_MSG_EQ(+hdr.GetQosQueueSize(), 0, "Unexpected buffer size");
4023 }
4024 continue;
4025 }
4026 }
4027 NS_TEST_EXPECT_MSG_EQ(ctsCount, (m_enableBsrp ? 0 : 2), "Unexpected number of CTS frames");
4028 NS_TEST_EXPECT_MSG_EQ(qosNullCount,
4029 (m_enableBsrp ? 4 : 2),
4030 "Unexpected number of QoS Null frames");
4031
4032 // we expect only one Basic Trigger Frame (sent on the same link as the first Trigger Frame),
4033 // because the buffer status reported on the other links by the non-EMLSR client is zero
4034 NS_TEST_ASSERT_MSG_EQ(m_txPsdus[index].psduMap.cbegin()->second->GetHeader(0).IsTrigger(),
4035 true,
4036 "Expected a Trigger Frame");
4037 NS_TEST_EXPECT_MSG_EQ(+m_txPsdus[index].linkId,
4038 +firstLinkId,
4039 "Unexpected link ID for Basic TF");
4040 CtrlTriggerHeader trigger;
4041 m_txPsdus[index].psduMap.cbegin()->second->GetPayload(0)->PeekHeader(trigger);
4042
4043 NS_TEST_EXPECT_MSG_EQ(+static_cast<uint8_t>(trigger.GetType()),
4044 +static_cast<uint8_t>(TriggerFrameType::BASIC_TRIGGER),
4045 "Unexpected Trigger Frame type");
4046
4047 // when BSRP TF is enabled, the non-EMLSR client has already communicated a buffer status of
4048 // zero, so it is not solicited by the AP through the Basic Trigger Frame. Otherwise, it is
4049 // solicited because buffer status was not known when the BSRP TF was prepared (before sending
4050 // MU-RTS)
4052 (m_enableBsrp ? 1 : 2),
4053 "Unexpected number of User Info fields for Basic Trigger Frame");
4054
4055 // Response(s) to the Basic Trigger Frame
4056 startIndex = ++index;
4057 for (; index < startIndex + (m_enableBsrp ? 1 : 2); ++index)
4058 {
4059 const auto& hdr = m_txPsdus[index].psduMap.cbegin()->second->GetHeader(0);
4060
4061 NS_TEST_EXPECT_MSG_EQ(hdr.IsQosData(), true, "Expected a QoS frame");
4062
4063 // EMLSR client sends a QoS Data frame, non-EMLSR client sends a QoS Null frame
4065 hdr.HasData(),
4066 (hdr.GetAddr2() == m_staMacs[0]->GetFrameExchangeManager(firstLinkId)->GetAddress()),
4067 "Unexpected type of QoS data frame");
4068
4069 if (hdr.HasData())
4070 {
4071 NS_TEST_EXPECT_MSG_EQ(m_txPsdus[index].txVector.IsUlMu(),
4072 true,
4073 "QoS Data frame should be sent in a TB PPDU");
4074 }
4075 }
4076
4077 // Finally, the AP MLD sends a Multi-STA BlockAck
4078 NS_TEST_EXPECT_MSG_EQ(m_txPsdus[index].psduMap.cbegin()->second->GetHeader(0).IsBlockAck(),
4079 true,
4080 "Expected a BlockAck frame");
4081 CtrlBAckResponseHeader blockAck;
4082 m_txPsdus[index].psduMap.cbegin()->second->GetPayload(0)->PeekHeader(blockAck);
4083 NS_TEST_EXPECT_MSG_EQ(blockAck.IsMultiSta(), true, "Expected a Multi-STA BlockAck");
4084}
4085
4088 std::string("Check EMLSR link switching (switchAuxPhy=") +
4089 std::to_string(params.switchAuxPhy) + ", resetCamStateAndInterruptSwitch=" +
4090 std::to_string(params.resetCamStateAndInterruptSwitch) +
4091 ", auxPhyMaxChWidth=" + std::to_string(params.auxPhyMaxChWidth) + "MHz )"),
4092 m_switchAuxPhy(params.switchAuxPhy),
4093 m_resetCamStateAndInterruptSwitch(params.resetCamStateAndInterruptSwitch),
4094 m_auxPhyMaxChWidth(params.auxPhyMaxChWidth),
4095 m_countQoSframes(0),
4096 m_countIcfFrames(0),
4097 m_countRtsFrames(0),
4098 m_txPsdusPos(0)
4099{
4100 m_nEmlsrStations = 1;
4102 m_linksToEnableEmlsrOn = {0, 1, 2}; // enable EMLSR on all links right after association
4103 m_mainPhyId = 1;
4104 m_establishBaDl = true;
4105 m_duration = Seconds(1);
4106 // when aux PHYs do not switch link, the main PHY switches back to its previous link after
4107 // a TXOP, hence the transition delay must exceed the channel switch delay (default: 250us)
4109}
4110
4111void
4113 uint8_t phyId,
4114 WifiConstPsduMap psduMap,
4115 WifiTxVector txVector,
4116 double txPowerW)
4117{
4118 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
4119 auto linkId = m_txPsdus.back().linkId;
4120
4121 auto psdu = psduMap.begin()->second;
4122 auto nodeId = mac->GetDevice()->GetNode()->GetId();
4123
4124 switch (psdu->GetHeader(0).GetType())
4125 {
4127 NS_ASSERT_MSG(nodeId > 0, "APs do not send AssocReq frames");
4128 NS_TEST_EXPECT_MSG_EQ(+linkId, +m_mainPhyId, "AssocReq not sent by the main PHY");
4129 break;
4130
4131 case WIFI_MAC_MGT_ACTION: {
4132 auto [category, action] = WifiActionHeader::Peek(psdu->GetPayload(0));
4133
4134 if (nodeId == 1 && category == WifiActionHeader::PROTECTED_EHT &&
4135 action.protectedEhtAction ==
4137 {
4138 // the EMLSR client is starting the transmission of the EML OMN frame;
4139 // temporarily block transmissions of QoS data frames from the AP MLD to the
4140 // non-AP MLD on all the links but the one used for ML setup, so that we know
4141 // that the first QoS data frame is sent on the link of the main PHY
4142 std::set<uint8_t> linksToBlock;
4143 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
4144 {
4145 if (id != m_mainPhyId)
4146 {
4147 linksToBlock.insert(id);
4148 }
4149 }
4150 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4151 AC_BE,
4153 m_staMacs[0]->GetAddress(),
4154 m_apMac->GetAddress(),
4155 {0},
4156 linksToBlock);
4157 }
4158 }
4159 break;
4160
4162 CheckInitialControlFrame(psduMap, txVector, linkId);
4163 break;
4164
4165 case WIFI_MAC_QOSDATA:
4166 CheckQosFrames(psduMap, txVector, linkId);
4167 break;
4168
4169 case WIFI_MAC_CTL_RTS:
4170 CheckRtsFrame(psduMap, txVector, linkId);
4171 break;
4172
4173 default:;
4174 }
4175}
4176
4177void
4179{
4180 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(m_switchAuxPhy));
4181 Config::SetDefault("ns3::EmlsrManager::ResetCamState",
4183 Config::SetDefault("ns3::AdvancedEmlsrManager::InterruptSwitch",
4185 Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth", UintegerValue(m_auxPhyMaxChWidth));
4186 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(45)));
4187
4189
4191 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); ++linkId)
4192 {
4193 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
4194 }
4195
4196 // use channels of different widths
4197 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0]})
4198 {
4199 mac->GetWifiPhy(0)->SetOperatingChannel(
4201 mac->GetWifiPhy(1)->SetOperatingChannel(
4203 mac->GetWifiPhy(2)->SetOperatingChannel(
4205 }
4206}
4207
4208void
4218
4219void
4221 const WifiTxVector& txVector,
4222 uint8_t linkId)
4223{
4225
4226 switch (m_countQoSframes)
4227 {
4228 case 1:
4229 // unblock transmissions on all links
4230 m_apMac->GetMacQueueScheduler()->UnblockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4231 AC_BE,
4233 m_staMacs[0]->GetAddress(),
4234 m_apMac->GetAddress(),
4235 {0},
4236 {0, 1, 2});
4237 // block transmissions on the link used for ML setup
4238 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4239 AC_BE,
4241 m_staMacs[0]->GetAddress(),
4242 m_apMac->GetAddress(),
4243 {0},
4244 {m_mainPhyId});
4245 // generate a new data packet, which will be sent on a link other than the one
4246 // used for ML setup, hence triggering a link switching on the EMLSR client
4247 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 0, 2, 1000));
4248 break;
4249 case 2:
4250 // block transmission on the link used to send this QoS data frame
4251 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4252 AC_BE,
4254 m_staMacs[0]->GetAddress(),
4255 m_apMac->GetAddress(),
4256 {0},
4257 {linkId});
4258 // generate a new data packet, which will be sent on the link that has not been used
4259 // so far, hence triggering another link switching on the EMLSR client
4260 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 0, 2, 1000));
4261 break;
4262 case 3:
4263 // block transmission on the link used to send this QoS data frame
4264 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4265 AC_BE,
4267 m_staMacs[0]->GetAddress(),
4268 m_apMac->GetAddress(),
4269 {0},
4270 {linkId});
4271 // unblock transmissions on the link used for ML setup
4272 m_apMac->GetMacQueueScheduler()->UnblockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4273 AC_BE,
4275 m_staMacs[0]->GetAddress(),
4276 m_apMac->GetAddress(),
4277 {0},
4278 {m_mainPhyId});
4279 // generate a new data packet, which will be sent again on the link used for ML setup,
4280 // hence triggering yet another link switching on the EMLSR client
4281 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 0, 2, 1000));
4282 break;
4283 case 4:
4284 // block transmissions on all links at non-AP MLD side
4285 m_staMacs[0]->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4286 AC_BE,
4288 m_apMac->GetAddress(),
4289 m_staMacs[0]->GetAddress(),
4290 {0},
4291 {0, 1, 2});
4292 // unblock transmissions on the link used for ML setup (non-AP MLD side)
4293 m_staMacs[0]->GetMacQueueScheduler()->UnblockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4294 AC_BE,
4296 m_apMac->GetAddress(),
4297 m_staMacs[0]->GetAddress(),
4298 {0},
4299 {m_mainPhyId});
4300 // trigger establishment of BA agreement with AP as recipient
4301 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 4, 1000));
4302 break;
4303 case 5:
4304 // unblock transmissions on all links at non-AP MLD side
4305 m_staMacs[0]->GetMacQueueScheduler()->UnblockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4306 AC_BE,
4308 m_apMac->GetAddress(),
4309 m_staMacs[0]->GetAddress(),
4310 {0},
4311 {0, 1, 2});
4312 // block transmissions on the link used for ML setup (non-AP MLD side)
4313 m_staMacs[0]->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4314 AC_BE,
4316 m_apMac->GetAddress(),
4317 m_staMacs[0]->GetAddress(),
4318 {0},
4319 {m_mainPhyId});
4320 // generate a new data packet, which will be sent on a link other than the one
4321 // used for ML setup, hence triggering a link switching on the EMLSR client
4322 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
4323 break;
4324 }
4325}
4326
4327/**
4328 * AUX PHY switching enabled (X = channel switch delay)
4329 *
4330 * |--------- aux PHY A ---------|------ main PHY ------|-------------- aux PHY B -------------
4331 * ┌───┐ ┌───┐
4332 * │ICF│ │QoS│
4333 * ──────────────────────────┴───┴┬───┬┴───┴┬──┬────────────────────────────────────────────────
4334 * [link 0] │CTS│ │BA│
4335 * └───┘ └──┘
4336 *
4337 *
4338 * |--------- main PHY ----------|------------------ aux PHY A ----------------|--- main PHY ---
4339 * ┌───┐ ┌───┐ ┌───┐ ┌───┐
4340 * │ICF│ │QoS│ │ICF│ │QoS│
4341 * ───┴───┴┬───┬┴───┴┬──┬──────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬──
4342 * [link 1]│CTS│ │BA│ │CTS│ │BA│
4343 * └───┘ └──┘ └───┘ └──┘
4344 *
4345 *
4346 * |--------------------- aux PHY B --------------------|------ main PHY ------|-- aux PHY A ---
4347 * ┌───┐ ┌───┐
4348 * │ICF│ │QoS│
4349 * ─────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬─────────────────────────
4350 * [link 2] │CTS│ │BA│
4351 * └───┘ └──┘
4352 *
4353 * ... continued ...
4354 *
4355 * |----------------------------------------- aux PHY B ---------------------------------------
4356 * ─────────────────────────────────────────────────────────────────────────────────────────────
4357 * [link 0]
4358 *
4359 * |--------- main PHY ----------|X|X|------------------------ aux PHY A ----------------------
4360 * ┌───┐
4361 * │ACK│
4362 * ──────────┬───┬┴───┴────────────────────────────────────────────────────────────────────────
4363 * [link 1] │QoS│
4364 * └───┘
4365 *
4366 * |-------- aux PHY A ----------|X|---------------------- main PHY ---------------------------
4367 * ┌──┐
4368 * │BA│
4369 * ────────────────────────┬───X──────┬───┬┴──┴────────────────────────────────────────────────
4370 * [link 2] │RTS│ │QoS│
4371 * └───┘ └───┘
4372 ************************************************************************************************
4373 *
4374 * AUX PHY switching disabled (X = channel switch delay)
4375 *
4376 * |------------------------------------------ aux PHY A ---------------------------------------
4377 * |-- main PHY --|X|
4378 * ┌───┐ ┌───┐
4379 * │ICF│ │QoS│
4380 * ──────────────────────────┴───┴┬───┬┴───┴┬──┬────────────────────────────────────────────────
4381 * [link 0] │CTS│ │BA│
4382 * └───┘ └──┘
4383 *
4384 * |-main|
4385 * |--------- main PHY ----------| |-PHY-| |------ main PHY ------
4386 * ┌───┐ ┌───┐ ┌───┐ ┌───┐
4387 * │ICF│ │QoS│ │ICF│ │QoS│
4388 * ───┴───┴┬───┬┴───┴┬──┬──────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬──
4389 * [link 1]│CTS│ │BA│ │CTS│ │BA│
4390 * └───┘ └──┘ └───┘ └──┘
4391 *
4392 *
4393 * |------------------------------------------ aux PHY B ---------------------------------------
4394 * |-- main PHY --|X|
4395 * ┌───┐ ┌───┐
4396 * │ICF│ │QoS│
4397 * ─────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬─────────────────────────
4398 * [link 2] │CTS│ │BA│
4399 * └───┘ └──┘
4400 *
4401 * ... continued ...
4402 *
4403 * |----------------------------------------- aux PHY A ---------------------------------------
4404 * ─────────────────────────────────────────────────────────────────────────────────────────────
4405 * [link 0]
4406 *
4407 * |-------- main PHY --------| |--- main PHY ---|
4408 * ┌───┐
4409 * │ACK│
4410 * ──────────┬───┬┴───┴────────────────────────────────────────────────────────────────────────
4411 * [link 1] │QoS│
4412 * └───┘
4413 *
4414 * |------------------------------------------ aux PHY B --------------------------------------
4415 * |X||X| |X|-------------- main PHY --------------
4416 * ┌───┐ ┌──┐
4417 * │CTS│ │BA│
4418 * ────────────────────────┬───X───────────────┬───┬┴───┴┬───┬┴──┴─────────────────────────────
4419 * [link 2] │RTS│ │RTS│ │QoS│
4420 * └───┘ └───┘ └───┘
4421 *
4422 */
4423
4424void
4426 const WifiTxVector& txVector,
4427 uint8_t linkId)
4428{
4429 if (++m_countIcfFrames == 1)
4430 {
4431 m_txPsdusPos = m_txPsdus.size() - 1;
4432 }
4433
4434 // the first ICF is sent to protect ADDBA_REQ for DL BA agreement, then one ICF is sent before
4435 // each of the 4 DL QoS Data frames; finally, another ICF is sent before the ADDBA_RESP for UL
4436 // BA agreement. Hence, at any time the number of ICF sent is always greater than or equal to
4437 // the number of QoS data frames sent.
4438 NS_TEST_EXPECT_MSG_GT_OR_EQ(m_countIcfFrames, m_countQoSframes, "Unexpected number of ICFs");
4439
4440 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
4441 auto phyRecvIcf = m_staMacs[0]->GetWifiPhy(linkId); // PHY receiving the ICF
4442
4443 auto currMainPhyLinkId = m_staMacs[0]->GetLinkForPhy(mainPhy);
4444 NS_TEST_ASSERT_MSG_EQ(currMainPhyLinkId.has_value(),
4445 true,
4446 "Didn't find the link on which the Main PHY is operating");
4447 NS_TEST_ASSERT_MSG_NE(phyRecvIcf,
4448 nullptr,
4449 "No PHY on the link where ICF " << m_countQoSframes << " was sent");
4450
4451 if (phyRecvIcf != mainPhy)
4452 {
4454 phyRecvIcf->GetChannelWidth(),
4456 "Aux PHY that received ICF "
4457 << m_countQoSframes << " is operating on a channel whose width exceeds the limit");
4458 }
4459
4460 // the first two ICFs (before ADDBA_REQ and before first DL QoS Data) and the ICF before the
4461 // ADDBA_RESP are received by the main PHY. If aux PHYs do not switch links, the ICF before
4462 // the last DL QoS Data is also received by the main PHY
4463 NS_TEST_EXPECT_MSG_EQ((phyRecvIcf == mainPhy),
4464 (m_countIcfFrames == 1 || m_countIcfFrames == 2 ||
4466 "Expecting that the ICF was received by the main PHY");
4467
4468 // if aux PHYs do not switch links, the main PHY is operating on its original link when
4469 // the transmission of an ICF starts
4470 NS_TEST_EXPECT_MSG_EQ(m_switchAuxPhy || currMainPhyLinkId == m_mainPhyId,
4471 true,
4472 "Main PHY is operating on an unexpected link ("
4473 << +currMainPhyLinkId.value() << ", expected " << +m_mainPhyId
4474 << ")");
4475
4476 auto txDuration =
4477 WifiPhy::CalculateTxDuration(psduMap, txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand());
4478
4479 // check that PHYs are operating on the expected link after the reception of the ICF
4480 Simulator::Schedule(txDuration + NanoSeconds(1), [=, this]() {
4481 // the main PHY must be operating on the link where ICF was sent
4482 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId),
4483 mainPhy,
4484 "PHY operating on link where ICF was sent is not the main PHY");
4485
4486 // the behavior of Aux PHYs depends on whether they switch channel or not
4487 if (m_switchAuxPhy)
4488 {
4489 if (mainPhy != phyRecvIcf)
4490 {
4491 NS_TEST_EXPECT_MSG_EQ(phyRecvIcf->IsStateSwitching(),
4492 true,
4493 "Aux PHY expected to switch channel");
4494 }
4495 Simulator::Schedule(phyRecvIcf->GetChannelSwitchDelay(), [=, this]() {
4496 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(*currMainPhyLinkId),
4497 phyRecvIcf,
4498 "The Aux PHY that received the ICF is expected to operate "
4499 "on the link where Main PHY was before switching channel");
4500 });
4501 }
4502 else
4503 {
4504 NS_TEST_EXPECT_MSG_EQ(phyRecvIcf->IsStateSwitching(),
4505 false,
4506 "Aux PHY is not expected to switch channel");
4507 NS_TEST_EXPECT_MSG_EQ(phyRecvIcf->GetPhyBand(),
4508 mainPhy->GetPhyBand(),
4509 "The Aux PHY that received the ICF is expected to operate "
4510 "on the same band as the Main PHY");
4511 }
4512 });
4513}
4514
4515void
4517 const WifiTxVector& txVector,
4518 uint8_t linkId)
4519{
4520 // corrupt the first RTS frame (sent by the EMLSR client)
4521 if (++m_countRtsFrames == 1)
4522 {
4523 auto psdu = psduMap.begin()->second;
4524 m_errorModel->SetList({psdu->GetPacket()->GetUid()});
4525
4526 // check that when CTS timeout occurs, the main PHY is switching
4528 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetWifiTxTimer().GetDelayLeft() -
4529 TimeStep(1),
4530 [=, this]() {
4531 // store the time to complete the current channel switch at CTS timeout
4532 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
4533 auto toCurrSwitchEnd = mainPhy->GetDelayUntilIdle() + TimeStep(1);
4534
4535 Simulator::Schedule(TimeStep(1), [=, this]() {
4536 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
4537 true,
4538 "Main PHY expected to be in SWITCHING state instead of "
4539 << mainPhy->GetState()->GetState());
4540
4541 // If main PHY channel switch can be interrupted, the main PHY should be back
4542 // operating on the preferred link after a channel switch delay. Otherwise, it
4543 // will be operating on the preferred link, if SwitchAuxPhy is false, or on the
4544 // link used to send the RTS, if SwitchAuxPhy is true, after the remaining
4545 // channel switching time plus the channel switch delay.
4547 ? m_mainPhyId
4548 : linkId;
4549 auto delayLeft = m_resetCamStateAndInterruptSwitch
4550 ? Time{0}
4551 : toCurrSwitchEnd; // time to complete current switch
4553 {
4554 // add the time to perform another channel switch
4555 delayLeft += mainPhy->GetChannelSwitchDelay();
4556 }
4557
4558 auto totalSwitchDelay =
4559 delayLeft + (mainPhy->GetChannelSwitchDelay() - toCurrSwitchEnd);
4560
4561 Simulator::Schedule(delayLeft - TimeStep(1), [=, this]() {
4562 // check if the MSD timer was running on the link left by the main PHY
4563 // before completing channel switch
4564 bool msdWasRunning = m_staMacs[0]
4565 ->GetEmlsrManager()
4566 ->GetElapsedMediumSyncDelayTimer(m_mainPhyId)
4567 .has_value();
4568
4569 Simulator::Schedule(TimeStep(2), [=, this]() {
4570 auto id = m_staMacs[0]->GetLinkForPhy(mainPhy);
4571 NS_TEST_EXPECT_MSG_EQ(id.has_value(),
4572 true,
4573 "Expected main PHY to operate on a link");
4575 newLinkId,
4576 "Main PHY is operating on an unexpected link");
4577 const auto startMsd =
4578 (totalSwitchDelay >
4580 const auto msdIsRunning = msdWasRunning || startMsd;
4582 m_staMacs[0],
4584 msdIsRunning,
4585 std::string("because total switch delay was ") +
4586 std::to_string(totalSwitchDelay.GetNanoSeconds()) + "ns");
4587 });
4588 });
4589 });
4590 });
4591 }
4592 // block transmissions on all other links at non-AP MLD side
4593 std::set<uint8_t> links{0, 1, 2};
4594 links.erase(linkId);
4595 m_staMacs[0]->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4596 AC_BE,
4598 m_apMac->GetAddress(),
4599 m_staMacs[0]->GetAddress(),
4600 {0},
4601 links);
4602}
4603
4604void
4606{
4607 NS_TEST_ASSERT_MSG_NE(m_txPsdusPos, 0, "BA agreement establishment not completed");
4608
4609 // Expected frame exchanges after ML setup and EML OMN exchange:
4610 // 1. (DL) ICF + CTS + ADDBA_REQ + ACK
4611 // 2. (UL) ADDBA_RESP + ACK
4612 // 3. (DL) ICF + CTS + DATA + BA
4613 // 4. (DL) ICF + CTS + DATA + BA
4614 // 5. (DL) ICF + CTS + DATA + BA
4615 // 6. (DL) ICF + CTS + DATA + BA
4616 // 7. (UL) ADDBA_REQ + ACK
4617 // 8. (DL) ICF + CTS + ADDBA_RESP + ACK
4618 // 9. (UL) DATA + BA
4619 // 10. (UL) RTS - CTS timeout
4620 // 11. (UL) (RTS + CTS + ) DATA + BA
4621
4622 // frame exchange 11 is protected if SwitchAuxPhy is false or (SwitchAuxPhy is true and) the
4623 // main PHY switch can be interrupted
4624 bool fe11protected = !m_switchAuxPhy || m_resetCamStateAndInterruptSwitch;
4625
4626 NS_TEST_EXPECT_MSG_EQ(m_countIcfFrames, 6, "Unexpected number of ICFs sent");
4627
4628 // frame exchanges without RTS because the EMLSR client sent the initial frame through main PHY
4629 const std::size_t nFrameExchNoRts = fe11protected ? 3 : 4;
4630
4631 const std::size_t nFrameExchWithRts = fe11protected ? 1 : 0;
4632
4634 m_txPsdusPos +
4635 m_countIcfFrames * 4 + /* frames in frame exchange with ICF */
4636 nFrameExchNoRts * 2 + /* frames in frame exchange without RTS */
4637 nFrameExchWithRts * 4 + /* frames in frame exchange with RTS */
4638 1, /* corrupted RTS */
4639 "Insufficient number of TX PSDUs");
4640
4641 // m_txPsdusPos points to the first ICF
4642 auto psduIt = std::next(m_txPsdus.cbegin(), m_txPsdusPos);
4643
4644 const std::size_t nFrameExchanges =
4645 m_countIcfFrames + nFrameExchNoRts + nFrameExchWithRts + 1 /* corrupted RTS */;
4646
4647 for (std::size_t i = 1; i <= nFrameExchanges; ++i)
4648 {
4649 if (i == 1 || (i >= 3 && i <= 6) || i == 8 || i == 10 || (i == 11 && fe11protected))
4650 {
4651 // frame exchanges with protection
4652 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
4653 (i < 9 ? psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsTrigger()
4654 : psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsRts())),
4655 true,
4656 "Expected a Trigger Frame (ICF)");
4657 psduIt++;
4658 if (i == 10)
4659 {
4660 continue; // corrupted RTS
4661 }
4662 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
4663 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsCts()),
4664 true,
4665 "Expected a CTS");
4666 psduIt++;
4667 }
4668
4669 if (i == 1 || i == 2 || i == 7 || i == 8) // frame exchanges with ADDBA REQ/RESP frames
4670 {
4671 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
4672 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsMgt()),
4673 true,
4674 "Expected a management frame");
4675 psduIt++;
4676 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
4677 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsAck()),
4678 true,
4679 "Expected a Normal Ack");
4680 }
4681 else
4682 {
4683 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
4684 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsQosData()),
4685 true,
4686 "Expected a QoS Data frame");
4687 psduIt++;
4688 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
4689 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsBlockAck()),
4690 true,
4691 "Expected a BlockAck");
4692 }
4693 psduIt++;
4694 }
4695}
4696
4698 : EmlsrOperationsTestBase(std::string("Check EMLSR link switching (auxPhyMaxChWidth=") +
4699 std::to_string(auxPhyMaxChWidth) + "MHz )"),
4700 m_auxPhyMaxChWidth(auxPhyMaxChWidth),
4701 m_channelSwitchDelay(MicroSeconds(75)),
4702 m_currMainPhyLinkId(0),
4703 m_nextMainPhyLinkId(0)
4704{
4705 m_nEmlsrStations = 1;
4707 m_linksToEnableEmlsrOn = {0, 1, 2}; // enable EMLSR on all links right after association
4708 m_mainPhyId = 1;
4709 m_establishBaUl = true;
4710 m_duration = Seconds(1);
4712}
4713
4714void
4722
4723void
4725{
4726 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(true));
4727 Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth", UintegerValue(m_auxPhyMaxChWidth));
4728 Config::SetDefault("ns3::EmlsrManager::AuxPhyMaxModClass", StringValue("EHT"));
4729 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(m_channelSwitchDelay));
4730
4732
4733 // use channels of different widths
4734 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
4735 {
4736 mac->GetWifiPhy(0)->SetOperatingChannel(
4738 mac->GetWifiPhy(1)->SetOperatingChannel(
4740 mac->GetWifiPhy(2)->SetOperatingChannel(
4742 }
4743}
4744
4745void
4747{
4748 m_staMacs[1]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 1, 1, 2000));
4749
4750 // force the transmission of the packet to happen now on the given link.
4751 // Multiple ScheduleNow calls are needed because Node::AddApplication() schedules a call to
4752 // Application::Initialize(), which schedules a call to Application::StartApplication(), which
4753 // schedules a call to PacketSocketClient::Send(), which finally generates the packet
4754 Simulator::ScheduleNow([=, this]() {
4755 Simulator::ScheduleNow([=, this]() {
4756 Simulator::ScheduleNow([=, this]() {
4757 m_staMacs[1]->GetFrameExchangeManager(linkId)->StartTransmission(
4758 m_staMacs[1]->GetQosTxop(AC_BE),
4759 m_staMacs[1]->GetWifiPhy(linkId)->GetChannelWidth());
4760 });
4761 });
4762 });
4763
4764 // check that the other MLD started transmitting on the correct link
4765 Simulator::Schedule(TimeStep(1), [=, this]() {
4766 NS_TEST_EXPECT_MSG_EQ(m_staMacs[1]->GetWifiPhy(linkId)->IsStateTx(),
4767 true,
4768 "At time " << Simulator::Now().As(Time::NS)
4769 << ", other MLD did not start transmitting on link "
4770 << +linkId);
4771 });
4772}
4773
4774void
4776{
4777 auto currMainPhyLinkId = m_staMacs[0]->GetLinkForPhy(m_mainPhyId);
4778 NS_TEST_ASSERT_MSG_EQ(currMainPhyLinkId.has_value(),
4779 true,
4780 "Main PHY is not operating on any link");
4781 m_currMainPhyLinkId = *currMainPhyLinkId;
4783
4784 // request the main PHY to switch to another link
4785 m_staMacs[0]->GetEmlsrManager()->SwitchMainPhy(
4787 false,
4790 EmlsrDlTxopIcfReceivedByAuxPhyTrace{}); // trace info not used
4791
4792 // the other MLD transmits a packet to the AP
4794
4795 // schedule another packet transmission slightly (10 us) before the end of aux PHY switch
4798 this,
4800
4801 // first checkpoint is after that the preamble of the PPDU has been received
4803}
4804
4805/**
4806 * ┌───────────────┐
4807 * [link X] │ other to AP │CP3
4808 * ──────────────────────────────┴───────────────┴──────────────────────────────────────────────
4809 * |------ main PHY ------| |------------------- aux PHY ---------------------
4810 * .\_ _/
4811 * . \_ _/
4812 * . \_ _/
4813 * . \_ _/
4814 * [link Y] . CP1 \/ CP2
4815 * .┌───────────────┐
4816 * .│ other to AP │
4817 * ─────────────────────────┴───────────────┴────────────────────────────────────────────────────
4818 * |------------ aux PHY ----------|---------------------- main PHY ----------------------------
4819 *
4820 */
4821
4822void
4824{
4825 // first checkpoint is after that the preamble of the first PPDU has been received
4826 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
4827
4828 // 1. Main PHY is switching
4829 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(), true, "Main PHY is not switching");
4830
4831 auto auxPhy = m_staMacs[0]->GetWifiPhy(m_nextMainPhyLinkId);
4832 NS_TEST_EXPECT_MSG_NE(mainPhy, auxPhy, "Main PHY is operating on an unexpected link");
4833
4834 // 2. Aux PHY is receiving the PHY header
4835 NS_TEST_EXPECT_MSG_EQ(auxPhy->IsReceivingPhyHeader(),
4836 true,
4837 "Aux PHY is not receiving a PHY header");
4838
4839 // 3. Main PHY dropped the preamble because it is switching
4840 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsReceivingPhyHeader(),
4841 false,
4842 "Main PHY is receiving a PHY header");
4843
4844 // 4. Channel access manager on destination link (Y) has been notified of CCA busy, but not
4845 // until the end of transmission (main PHY dropped the preamble and notified CCA busy until
4846 // end of transmission but the channel access manager on link Y does not yet have a listener
4847 // attached to the main PHY; aux PHY notified CCA busy until the end of the PHY header field
4848 // being received)
4849 const auto caManager = m_staMacs[0]->GetChannelAccessManager(m_nextMainPhyLinkId);
4850 const auto endTxTime = m_staMacs[1]->GetChannelAccessManager(m_nextMainPhyLinkId)->m_lastTxEnd;
4851 NS_TEST_ASSERT_MSG_EQ(caManager->m_lastBusyEnd.contains(WIFI_CHANLIST_PRIMARY),
4852 true,
4853 "No CCA information for primary20 channel");
4855 caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
4857 "ChannelAccessManager on destination link not notified of CCA busy");
4859 caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
4860 endTxTime,
4861 "ChannelAccessManager on destination link notified of CCA busy until end of transmission");
4862
4863 // second checkpoint is after that the main PHY completed the link switch
4864 Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1),
4866 this);
4867}
4868
4869void
4871{
4872 // second checkpoint is after that the main PHY completed the link switch. The channel access
4873 // manager on destination link (Y) is expected to be notified by the main PHY that medium is
4874 // busy until the end of the ongoing transmission
4875 const auto caManager = m_staMacs[0]->GetChannelAccessManager(m_nextMainPhyLinkId);
4876 const auto endTxTime = m_staMacs[1]->GetChannelAccessManager(m_nextMainPhyLinkId)->m_lastTxEnd;
4877 NS_TEST_ASSERT_MSG_EQ(caManager->m_lastBusyEnd.contains(WIFI_CHANLIST_PRIMARY),
4878 true,
4879 "No CCA information for primary20 channel");
4881 caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
4883 "ChannelAccessManager on destination link not notified of CCA busy");
4884 NS_TEST_EXPECT_MSG_GT_OR_EQ(caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
4885 endTxTime,
4886 "ChannelAccessManager on destination link not notified of CCA busy "
4887 "until end of transmission");
4888
4889 // third checkpoint is after that the aux PHY completed the link switch
4891}
4892
4893void
4895{
4896 // third checkpoint is after that the aux PHY completed the link switch. The channel access
4897 // manager on source link (X) is expected to be notified by the aux PHY that medium is
4898 // busy until the end of the ongoing transmission (even if the aux PHY was not listening to
4899 // link X when transmission started, its interface on link X recorded the transmission)
4900 const auto caManager = m_staMacs[0]->GetChannelAccessManager(m_currMainPhyLinkId);
4901 const auto endTxTime = m_staMacs[1]->GetChannelAccessManager(m_currMainPhyLinkId)->m_lastTxEnd;
4902 NS_TEST_ASSERT_MSG_EQ(caManager->m_lastBusyEnd.contains(WIFI_CHANLIST_PRIMARY),
4903 true,
4904 "No CCA information for primary20 channel");
4905 NS_TEST_EXPECT_MSG_GT_OR_EQ(caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
4907 "ChannelAccessManager on source link not notified of CCA busy");
4908 NS_TEST_EXPECT_MSG_GT_OR_EQ(caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
4909 endTxTime,
4910 "ChannelAccessManager on source link not notified of CCA busy "
4911 "until end of transmission");
4912}
4913
4915 : TestSuite("wifi-emlsr", Type::UNIT)
4916{
4917 AddTestCase(new EmlOperatingModeNotificationTest(), TestCase::Duration::QUICK);
4918 AddTestCase(new EmlOmnExchangeTest({1, 2}, MicroSeconds(0)), TestCase::Duration::QUICK);
4919 AddTestCase(new EmlOmnExchangeTest({1, 2}, MicroSeconds(2048)), TestCase::Duration::QUICK);
4920 AddTestCase(new EmlOmnExchangeTest({0, 1, 2, 3}, MicroSeconds(0)), TestCase::Duration::QUICK);
4921 AddTestCase(new EmlOmnExchangeTest({0, 1, 2, 3}, MicroSeconds(2048)),
4922 TestCase::Duration::QUICK);
4923 for (const auto& emlsrLinks :
4924 {std::set<uint8_t>{0, 1, 2}, std::set<uint8_t>{1, 2}, std::set<uint8_t>{0, 1}})
4925 {
4927 0,
4928 emlsrLinks,
4929 {MicroSeconds(32)},
4930 {MicroSeconds(32)},
4931 MicroSeconds(512),
4932 true /* putAuxPhyToSleep */}),
4933 TestCase::Duration::QUICK);
4935 1,
4936 emlsrLinks,
4937 {MicroSeconds(64)},
4938 {MicroSeconds(64)},
4939 MicroSeconds(512),
4940 false /* putAuxPhyToSleep */}),
4941 TestCase::Duration::QUICK);
4943 2,
4944 emlsrLinks,
4945 {MicroSeconds(128), MicroSeconds(256)},
4946 {MicroSeconds(128), MicroSeconds(256)},
4947 MicroSeconds(512),
4948 true /* putAuxPhyToSleep */}),
4949 TestCase::Duration::QUICK);
4950 }
4951
4952 for (auto genBackoffAndUseAuxPhyCca : {true, false})
4953 {
4954 for (auto nSlotsLeft : std::initializer_list<uint8_t>{0, 4})
4955 {
4956 AddTestCase(new EmlsrUlTxopTest({{0, 1, 2},
4957 MHz_u{40},
4958 MHz_u{20},
4959 MicroSeconds(5504),
4960 3,
4961 genBackoffAndUseAuxPhyCca,
4962 nSlotsLeft,
4963 true, /* putAuxPhyToSleep */
4964 false /* switchMainPhyBackDelayTimeout */}),
4965 TestCase::Duration::QUICK);
4966 AddTestCase(new EmlsrUlTxopTest({{0, 1},
4967 MHz_u{40},
4968 MHz_u{20},
4969 MicroSeconds(5504),
4970 1,
4971 genBackoffAndUseAuxPhyCca,
4972 nSlotsLeft,
4973 false, /* putAuxPhyToSleep */
4974 true /* switchMainPhyBackDelayTimeout */}),
4975 TestCase::Duration::QUICK);
4976 }
4977 }
4978
4979 for (bool switchAuxPhy : {true, false})
4980 {
4981 for (bool resetCamStateAndInterruptSwitch : {true, false})
4982 {
4983 for (auto auxPhyMaxChWidth : {MHz_u{20}, MHz_u{40}, MHz_u{80}, MHz_u{160}})
4984 {
4986 {switchAuxPhy, resetCamStateAndInterruptSwitch, auxPhyMaxChWidth}),
4987 TestCase::Duration::QUICK);
4988 }
4989 }
4990 }
4991
4992 AddTestCase(new EmlsrUlOfdmaTest(false), TestCase::Duration::QUICK);
4993 AddTestCase(new EmlsrUlOfdmaTest(true), TestCase::Duration::QUICK);
4994
4995 AddTestCase(new EmlsrCcaBusyTest(MHz_u{20}), TestCase::Duration::QUICK);
4996 AddTestCase(new EmlsrCcaBusyTest(MHz_u{80}), TestCase::Duration::QUICK);
4997}
4998
4999static 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.
void DoRun() override
Implementation to actually run this TestCase.
Definition wifi-eml