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 m_5thQosFrameExpWidth(0)
2597{
2598 m_nEmlsrStations = 1;
2600 m_linksToEnableEmlsrOn = params.linksToEnableEmlsrOn;
2601 m_mainPhyId = 1;
2602
2603 // when aux PHYs do not switch link, the main PHY switches back to its previous link after
2604 // a TXOP, hence the transition delay must exceed the channel switch delay (default: 250us)
2606 m_establishBaDl = true;
2607 m_establishBaUl = true;
2608 m_putAuxPhyToSleep = params.putAuxPhyToSleep;
2609 m_duration = Seconds(1);
2610
2611 NS_ABORT_MSG_IF(params.linksToEnableEmlsrOn.size() < 2,
2612 "This test requires at least two links to be configured as EMLSR links");
2613 for (uint8_t id = 0; id < 3; id++)
2614 {
2615 if (!m_emlsrLinks.contains(id))
2616 {
2617 // non-EMLSR link found
2618 m_nonEmlsrLink = id;
2619 break;
2620 }
2621 }
2622}
2623
2624void
2626{
2627 Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth",
2629 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(false));
2630 Config::SetDefault("ns3::AdvancedEmlsrManager::UseAuxPhyCca", BooleanValue(m_useAuxPhyCca));
2631 Config::SetDefault("ns3::AdvancedEmlsrManager::SwitchMainPhyBackDelay",
2633 Config::SetDefault("ns3::EhtConfiguration::MediumSyncDuration",
2635 Config::SetDefault("ns3::EhtConfiguration::MsdMaxNTxops", UintegerValue(m_msdMaxNTxops));
2636 Config::SetDefault("ns3::ChannelAccessManager::GenerateBackoffIfTxopWithoutTx",
2638 Config::SetDefault("ns3::ChannelAccessManager::NSlotsLeft", UintegerValue(m_nSlotsLeftAlert));
2639 // Channel switch delay should be less than RTS TX time + SIFS + CTS TX time, otherwise
2640 // UL TXOPs cannot be initiated by aux PHYs
2641 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(75)));
2642 Config::SetDefault("ns3::WifiPhy::NotifyMacHdrRxEnd", BooleanValue(true));
2643
2645
2646 m_staMacs[0]->GetQosTxop(AC_BE)->TraceConnectWithoutContext(
2647 "BackoffTrace",
2649
2650 uint8_t linkId = 0;
2651 // configure channels of the given width
2653 {
2654 MHz_u bw{20};
2655 uint8_t number = band == WIFI_PHY_BAND_5GHZ ? 36 : 1;
2656
2657 auto width =
2658 std::min(m_channelWidth, band == WIFI_PHY_BAND_2_4GHZ ? MHz_u{40} : MHz_u{160});
2659 while (bw < width)
2660 {
2661 bw *= 2;
2662 number += Count20MHzSubchannels(bw);
2663 }
2664
2665 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0]})
2666 {
2667 mac->GetWifiPhy(linkId)->SetOperatingChannel(
2668 WifiPhy::ChannelTuple{number, width, band, 0});
2669 }
2670 linkId++;
2671 }
2672
2673 // install post reception error model on the AP affiliated with the AP MLD and operating on
2674 // the same link as the main PHY of the EMLSR client
2676 m_apMac->GetWifiPhy(m_mainPhyId)->SetPostReceptionErrorModel(m_errorModel);
2677}
2678
2679void
2681{
2682 NS_LOG_INFO("Backoff value " << backoff << " generated by EMLSR client on link " << +linkId
2683 << "\n");
2684 if (linkId != m_mainPhyId)
2685 {
2686 return; // we are only interested in backoff on main PHY link
2687 }
2688
2689 if (m_backoffEndTime)
2690 {
2692 {
2693 // another backoff value while checkBackoffStarted is true is generated only if
2694 // GenerateBackoffIfTxopWithoutTx is true
2697 true,
2698 "Another backoff value should not be generated while the main PHY link is blocked");
2699
2702 "Backoff generated at unexpected time");
2703 }
2704 else
2705 {
2706 // we are done checking the backoff
2707 m_backoffEndTime.reset();
2708 }
2709 }
2710
2712 {
2713 if (!m_backoffEndTime.has_value())
2714 {
2715 // this is the first time we set m_backoffEndTime, which is done right after receiving
2716 // a BlockAck, thus we have to wait an AIFS before invoking backoff
2718 m_staMacs[0]->GetChannelAccessManager(linkId)->GetSifs() +
2719 m_staMacs[0]->GetQosTxop(AC_BE)->GetAifsn(linkId) *
2720 m_staMacs[0]->GetChannelAccessManager(linkId)->GetSlot();
2721 }
2722 else
2723 {
2724 // we get here when the backoff expired but no transmission occurred, thus we have
2725 // generated a new backoff value and we will start decrementing the counter in a slot
2727 Simulator::Now() + m_staMacs[0]->GetChannelAccessManager(linkId)->GetSlot();
2728 }
2729 // add the time corresponding to the generated number of slots
2730 m_backoffEndTime.value() +=
2731 backoff * m_staMacs[0]->GetChannelAccessManager(linkId)->GetSlot();
2732 NS_LOG_DEBUG("Expected backoff end time = " << m_backoffEndTime->As(Time::US) << "\n");
2733 }
2734}
2735
2736void
2738 uint8_t phyId,
2739 WifiConstPsduMap psduMap,
2740 WifiTxVector txVector,
2741 double txPowerW)
2742{
2743 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2744 auto linkId = m_txPsdus.back().linkId;
2745
2746 auto psdu = psduMap.begin()->second;
2747 auto nodeId = mac->GetDevice()->GetNode()->GetId();
2748
2749 switch (psdu->GetHeader(0).GetType())
2750 {
2752 NS_ASSERT_MSG(nodeId > 0, "APs do not send AssocReq frames");
2753 NS_TEST_EXPECT_MSG_EQ(+linkId, +m_mainPhyId, "AssocReq not sent by the main PHY");
2754 break;
2755
2756 case WIFI_MAC_CTL_RTS:
2757 CheckRtsFrames(*psdu->begin(), txVector, linkId);
2758 break;
2759
2760 case WIFI_MAC_CTL_CTS:
2761 CheckCtsFrames(*psdu->begin(), txVector, linkId);
2762 break;
2763
2764 case WIFI_MAC_QOSDATA:
2765 CheckQosFrames(psduMap, txVector, linkId);
2766 break;
2767
2769 CheckBlockAck(psduMap, txVector, linkId);
2770 break;
2771
2772 default:;
2773 }
2774}
2775
2776void
2778{
2779 // initially, we prevent transmissions on aux PHY links
2780 auto auxPhyLinks = m_staMacs[0]->GetSetupLinkIds();
2781 auxPhyLinks.erase(m_mainPhyId);
2782 if (m_nonEmlsrLink)
2783 {
2784 auxPhyLinks.erase(*m_nonEmlsrLink);
2785 }
2786 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2787 m_apMac->GetAddress(),
2788 auxPhyLinks);
2789
2790 // Association, Block Ack agreement establishment and enabling EMLSR mode have been done.
2791 // After 50ms, schedule:
2792 // - block of transmissions on the link where the main PHY is operating and on the non-EMLSR
2793 // link (if any)
2794 // - the generation of two UL packets
2795 // - after m_unblockMainPhyLinkDelay, unblock transmissions on the link where the main PHY
2796 // is operating, so that the first data frame is transmitted on that link
2798 std::set<uint8_t> linkIds;
2799 linkIds.insert(*m_staMacs[0]->GetLinkForPhy(m_mainPhyId));
2800 if (m_nonEmlsrLink)
2801 {
2802 linkIds.insert(*m_nonEmlsrLink);
2803 }
2804 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2805 m_apMac->GetAddress(),
2806 linkIds);
2807
2808 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
2809 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
2811
2813 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2814 m_apMac->GetAddress(),
2815 {*m_staMacs[0]->GetLinkForPhy(m_mainPhyId)});
2816 });
2817 });
2818}
2819
2820void
2822 const WifiTxVector& txVector,
2823 uint8_t linkId)
2824{
2826
2827 auto txDuration =
2828 WifiPhy::CalculateTxDuration(psduMap, txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2829
2830 switch (m_countQoSframes)
2831 {
2832 case 1:
2833 case 2:
2834 // do nothing, these are the QoS data frames sent to establish BA agreements in DL and UL
2835 // direction
2836 break;
2837 case 3:
2838 // first UL data frame (transmitted by the main PHY)
2839 if (m_nonEmlsrLink)
2840 {
2841 // generate data packets for another UL data frame, which will be sent on the
2842 // non-EMLSR link
2843 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
2844 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
2845 GetApplication(UPLINK, 0, 2, 1000));
2846
2847 // unblock transmissions on the non-EMLSR link once the two packets are queued
2848 Simulator::ScheduleNow([=, this]() {
2849 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2850 m_apMac->GetAddress(),
2851 {*m_nonEmlsrLink});
2852 });
2853 }
2854
2855 // check that other EMLSR links are now blocked on the EMLSR client and on the AP MLD
2856 // after this QoS data frame is received
2857 Simulator::ScheduleNow([=, this]() {
2858 auto phyHdrTxTime = WifiPhy::CalculatePhyPreambleAndHeaderDuration(txVector);
2859 auto macHdrSize = (*psduMap.at(SU_STA_ID)->begin())->GetHeader().GetSerializedSize() +
2860 4 /* A-MPDU subframe header size */;
2861 auto macHdrTxTime =
2862 DataRate(txVector.GetMode().GetDataRate(txVector)).CalculateBytesTxTime(macHdrSize);
2863
2864 for (auto id : m_staMacs[0]->GetLinkIds())
2865 {
2867 m_staMacs[0],
2868 m_apMac->GetAddress(),
2869 id,
2870 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2871 id != m_staMacs[0]->GetLinkForPhy(m_mainPhyId) && m_staMacs[0]->IsEmlsrLink(id),
2872 "Checking EMLSR links on EMLSR client while sending the first data frame",
2873 false);
2874
2875 Simulator::Schedule(phyHdrTxTime + macHdrTxTime + MicroSeconds(1), [=, this]() {
2877 m_staMacs[0]->GetAddress(),
2878 id,
2879 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2880 id != m_staMacs[0]->GetLinkForPhy(m_mainPhyId) &&
2881 m_staMacs[0]->IsEmlsrLink(id),
2882 "Checking EMLSR links on AP MLD right after receiving the MAC "
2883 "header of the first data frame");
2884 });
2885
2888 [=, this]() {
2890 m_apMac,
2891 m_staMacs[0]->GetAddress(),
2892 id,
2893 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2894 id != m_staMacs[0]->GetLinkForPhy(m_mainPhyId) &&
2895 m_staMacs[0]->IsEmlsrLink(id),
2896 "Checking EMLSR links on AP MLD after sending the first data frame");
2897 });
2898 }
2899 });
2900
2901 if (m_nonEmlsrLink)
2902 {
2903 break;
2904 }
2905 m_countQoSframes++; // if all EMLSR links, next case is already executed now
2906 [[fallthrough]];
2907 case 4:
2908 // check that other EMLSR links are now blocked on the EMLSR client and on the AP MLD
2909 // after this QoS data frame is received
2910 Simulator::ScheduleNow([=, this]() {
2911 // make aux PHYs capable of transmitting frames
2912 auto auxPhyLinks = m_staMacs[0]->GetSetupLinkIds();
2913 auxPhyLinks.erase(m_mainPhyId);
2914 if (m_nonEmlsrLink)
2915 {
2916 auxPhyLinks.erase(*m_nonEmlsrLink);
2917 }
2918 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2919 m_apMac->GetAddress(),
2920 auxPhyLinks);
2921
2922 // block transmissions on the link where the main PHY is operating
2923 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2924 m_apMac->GetAddress(),
2925 {*m_staMacs[0]->GetLinkForPhy(m_mainPhyId)});
2926
2927 // generate data packets for another UL data frame, which will be sent on a link on
2928 // which an aux PHY is operating
2929 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
2930 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
2931 GetApplication(UPLINK, 0, 2, 1000));
2932 });
2933 break;
2934 case 5:
2935 // check that other EMLSR links are now blocked on both the EMLSR client and the AP MLD
2936 Simulator::ScheduleNow([=, this]() {
2937 for (auto id : m_staMacs[0]->GetLinkIds())
2938 {
2940 m_staMacs[0],
2941 m_apMac->GetAddress(),
2942 id,
2943 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2944 id != linkId && m_staMacs[0]->IsEmlsrLink(id),
2945 "Checking EMLSR links on EMLSR client while sending the second data frame",
2946 false);
2947
2949 m_apMac,
2950 m_staMacs[0]->GetAddress(),
2951 id,
2952 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2953 id != linkId && m_staMacs[0]->IsEmlsrLink(id),
2954 "Checking EMLSR links on AP MLD while sending the second data frame",
2955 false);
2956 }
2957
2958 // unblock transmission on the link where the main PHY is operating
2959 m_staMacs[0]->GetMacQueueScheduler()->UnblockQueues(
2960 WifiQueueBlockedReason::TID_NOT_MAPPED,
2961 AC_BE,
2963 m_apMac->GetAddress(),
2964 m_staMacs[0]->GetAddress(),
2965 {0},
2966 {m_mainPhyId});
2967 });
2968 break;
2969 }
2970}
2971
2972void
2974 const WifiTxVector& txVector,
2975 uint8_t linkId)
2976{
2978
2979 auto auxPhyLinks = m_staMacs[0]->GetSetupLinkIds();
2980 auxPhyLinks.erase(m_mainPhyId);
2981 if (m_nonEmlsrLink)
2982 {
2983 auxPhyLinks.erase(*m_nonEmlsrLink);
2984 }
2985
2986 auto txDuration =
2987 WifiPhy::CalculateTxDuration(psduMap, txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2988
2989 // in this test, BlockAck frames terminates TXOP, thus aux PHYs shall be in sleep mode before
2990 // the end of BlockAck reception and awake right afterwards
2991 if (linkId != m_nonEmlsrLink)
2992 {
2993 Simulator::Schedule(txDuration - TimeStep(1),
2995 this,
2996 m_staMacs[0],
2997 true);
2998 Simulator::Schedule(txDuration + TimeStep(1),
3000 this,
3001 m_staMacs[0],
3002 false);
3003
3004 // if the TXOP has been carried out on a link other than the preferred link, the main PHY
3005 // switches back to the preferred link when the TXOP ends
3006 if (m_staMacs[0]->GetLinkForPhy(m_mainPhyId) != linkId)
3007 {
3008 Simulator::Schedule(txDuration + TimeStep(1), [=, this]() {
3009 // check the traced remaining time before calling CheckMainPhyTraceInfo
3010 if (const auto traceInfoIt = m_traceInfo.find(0);
3011 traceInfoIt != m_traceInfo.cend() &&
3012 traceInfoIt->second->GetName() == "TxopEnded")
3013 {
3014 const auto& traceInfo =
3015 static_cast<const EmlsrTxopEndedTrace&>(*traceInfoIt->second);
3017 traceInfo.remTime,
3018 Time{0},
3019 "Expected null remaining time because TXOP ended regularly");
3020 }
3021
3022 CheckMainPhyTraceInfo(0, "TxopEnded", linkId, m_mainPhyId);
3023 });
3024 }
3025 }
3026
3027 switch (m_countBlockAck)
3028 {
3029 case 1:
3030 case 2:
3031 // do nothing, these are BlockAcks in response to the QoS data frames sent to establish
3032 // BA agreements in DL and UL direction
3033 break;
3034 case 3:
3035 if (linkId == m_nonEmlsrLink)
3036 {
3037 // this BlockAck has been sent on the non-EMLSR link, ignore it
3038 break;
3039 }
3040 m_checkBackoffStarted = true;
3041 if (!m_nonEmlsrLink)
3042 {
3043 m_countBlockAck++; // if all EMLSR links, next case is already executed now
3044 }
3045 [[fallthrough]];
3046 case 4:
3047 if (m_nonEmlsrLink && m_countBlockAck == 4)
3048 {
3049 // block transmissions on the non-EMLSR link
3050 Simulator::Schedule(txDuration + NanoSeconds(1), [=, this]() {
3051 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
3052 m_apMac->GetAddress(),
3053 {*m_nonEmlsrLink});
3054 });
3055 }
3056 if (linkId == m_nonEmlsrLink)
3057 {
3058 // this BlockAck has been sent on the non-EMLSR link, ignore it
3059 break;
3060 }
3061 m_checkBackoffStarted = true;
3062 break;
3063 case 5:
3064 // Block Ack in response to the second data frame sent by the EMLSR client on EMLSR links.
3065 // Check that MediumSyncDelay timer starts running on the link where the main PHY switches
3066 // to when the channel switch is completed
3068 txDuration + m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId)->GetChannelSwitchDelay() +
3069 NanoSeconds(1),
3070 [=, this]() {
3071 auto elapsed =
3072 m_staMacs[0]->GetEmlsrManager()->GetElapsedMediumSyncDelayTimer(m_mainPhyId);
3074 elapsed.has_value(),
3075 true,
3076 "MediumSyncDelay timer not running on link where main PHY is operating");
3078 m_staMacs[0]->GetEmlsrManager()->GetMediumSyncDuration() -
3079 *elapsed;
3080 });
3081
3082 // Check that the number of backoff slots is not changed since the beginning of the TXOP
3083 Simulator::Schedule(txDuration, [=, this]() {
3084 m_checkBackoffStarted = false;
3086 true,
3087 "Backoff end time should have been calculated");
3088 // when this BlockAck is received, the TXOP ends and the main PHY link is unblocked,
3089 // which causes a new backoff timer to be generated if the backoff timer is not
3090 // already running
3092 });
3093
3094 // make aux PHYs not capable of transmitting frames
3095 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
3096 m_apMac->GetAddress(),
3097 auxPhyLinks);
3098
3099 // generate data packets for another UL data frame, which will be sent on the link where
3100 // the main PHY is operating
3101 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
3102 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
3103 break;
3104 case 6: {
3105 // block transmission on the main PHY link and on the non-EMLSR link (if any), so that
3106 // the next QoS frames are sent on a link where an aux PHY is operating
3107 std::set<uint8_t> linkIds{m_mainPhyId};
3108 if (m_nonEmlsrLink)
3109 {
3110 linkIds.insert(*m_nonEmlsrLink);
3111 }
3112 m_staMacs[0]->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
3113 AC_BE,
3115 m_apMac->GetAddress(),
3116 m_staMacs[0]->GetAddress(),
3117 {0},
3118 linkIds);
3119 }
3120 // make sure aux PHYs are capable of transmitting frames
3121 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
3122 m_apMac->GetAddress(),
3123 auxPhyLinks);
3124
3125 // generate data packets for another UL data frame
3126 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
3127 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
3128 break;
3129 case 7:
3130 // make the aux PHY(s) not capable of transmitting frames
3131 m_staMacs[0]->GetEmlsrManager()->SetAuxPhyTxCapable(false);
3132 if (!m_nonEmlsrLink)
3133 {
3134 // if there are two auxiliary links, set MediumSyncDuration to zero so that the
3135 // next UL QoS data frame is not protected also in case it is transmitted on the
3136 // auxiliary link other than the one on which the last frame exchange occurred
3137 m_staMacs[0]->GetEmlsrManager()->SetMediumSyncDuration(Seconds(0));
3138 }
3139
3140 // generate a very large backoff for the preferred link, so that when an aux PHY gains a
3141 // TXOP, it requests the main PHY to switch to its link to transmit the frames
3142 m_staMacs[0]->GetQosTxop(AC_BE)->StartBackoffNow(100, m_mainPhyId);
3143
3144 // events to be scheduled at the end of the BlockAck response
3145 Simulator::Schedule(txDuration + NanoSeconds(1), [=, this]() {
3146 // check that the main PHY switches to its preferred link
3147 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
3148
3149 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
3150 true,
3151 "Main PHY is not switching at time "
3152 << Simulator::Now().As(Time::NS));
3153
3154 // events to be scheduled when the first main PHY channel switch is completed
3155 Simulator::Schedule(mainPhy->GetChannelSwitchDelay(), [=, this]() {
3156 // either the main PHY is operating on the preferred link or it is switching again
3157 auto mainPhyLinkid = m_staMacs[0]->GetLinkForPhy(mainPhy);
3158 if (mainPhyLinkid)
3159 {
3160 NS_TEST_EXPECT_MSG_EQ(+mainPhyLinkid.value(),
3161 +m_mainPhyId,
3162 "Main PHY expected to operate on the preferred link");
3163 }
3164 else
3165 {
3166 NS_TEST_EXPECT_MSG_EQ(
3167 mainPhy->IsStateSwitching(),
3168 true,
3169 "Main PHY is not operating on a link and it is not switching at time "
3170 << Simulator::Now().As(Time::NS));
3171 }
3172
3173 auto acBe = m_staMacs[0]->GetQosTxop(AC_BE);
3174
3175 // find the min remaining backoff time on auxiliary links for AC BE
3176 auto minBackoff = Time::Max();
3177 Time slot{0};
3178 for (uint8_t id = 0; id < m_staMacs[0]->GetNLinks(); id++)
3179 {
3180 if (!m_staMacs[0]->GetWifiPhy(id))
3181 {
3182 continue; // no PHY on this link
3183 }
3184
3185 if (auto backoff =
3186 m_staMacs[0]->GetChannelAccessManager(id)->GetBackoffEndFor(acBe);
3187 id != m_mainPhyId && m_staMacs[0]->IsEmlsrLink(id) && backoff < minBackoff)
3188 {
3189 minBackoff = backoff;
3190 slot = m_staMacs[0]->GetWifiPhy(id)->GetSlot();
3191 }
3192 }
3193
3194 // if the backoff on a link has expired before the end of the main PHY channel
3195 // switch, the main PHY will be requested to switch again no later than the first
3196 // slot boundary after the end of the channel switch. Otherwise, it will be
3197 // requested to switch when the backoff expires or when the backoff counter reaches
3198 // the configured number of slots
3199 auto expected2ndSwitchDelay =
3200 (minBackoff <= Simulator::Now()) ? mainPhy->GetSlot()
3201 : m_nSlotsLeftAlert > 0
3202 ? Max(minBackoff - m_nSlotsLeftAlert * slot - Simulator::Now(), Time{0})
3203 : (minBackoff - Simulator::Now());
3204
3205 // check that the main PHY is requested to switch to an auxiliary link after
3206 // the expected delay
3207 Simulator::Schedule(expected2ndSwitchDelay + NanoSeconds(1), [=, this]() {
3208 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
3209 true,
3210 "Main PHY is not switching at time "
3211 << Simulator::Now().As(Time::NS));
3212 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetLinkForPhy(mainPhy).has_value(),
3213 false,
3214 "Main PHY should not be operating on a link because it "
3215 "should be switching to an auxiliary link");
3216 // check that the appropriate trace info was received
3218 "UlTxopAuxPhyNotTxCapable",
3219 std::nullopt,
3220 0,
3221 false,
3222 false);
3223
3224 const auto delayUntilIdle = mainPhy->GetDelayUntilIdle();
3225 auto startTimerDelay = delayUntilIdle;
3226
3228 {
3229 TimeValue switchMainPhyBackDelay;
3230 m_staMacs[0]->GetEmlsrManager()->GetAttribute("SwitchMainPhyBackDelay",
3231 switchMainPhyBackDelay);
3232
3233 // If nSlotsAlert is 0, the decision whether to start the switch back timer
3234 // is taken at the end of the PIFS period during which we perform CCA and
3235 // NAV check, which coincides with the end of the channel switch or is a
3236 // PIFS afterwards, depending on whether aux PHY CCA is used. Therefore,
3237 // before the end of the CCA and NAV check period we have to make the medium
3238 // busy on the link the main PHY is switching to. Given that we do not know
3239 // which link it is, we set the NAV on all links.
3240 // If nSlotsAlert > 0, the decision whether to start the switch back timer
3241 // is taken at the end of the channel switch and it is needed that the time
3242 // until the backoff end is at least a PIFS to start the switch back timer.
3243 auto endCcaNavCheckDelay = delayUntilIdle;
3244
3245 for (uint8_t id = 0; id < m_staMacs[0]->GetNLinks(); ++id)
3246 {
3247 if (auto phy = m_staMacs[0]->GetWifiPhy(id))
3248 {
3249 if (!m_useAuxPhyCca && m_nSlotsLeftAlert == 0)
3250 {
3251 endCcaNavCheckDelay =
3252 Max(endCcaNavCheckDelay, delayUntilIdle + phy->GetPifs());
3253 }
3254
3255 m_staMacs[0]->GetChannelAccessManager(id)->NotifyNavStartNow(
3256 endCcaNavCheckDelay + TimeStep(1));
3257 }
3258 }
3259 startTimerDelay = endCcaNavCheckDelay;
3260
3261 // when the SwitchMainPhyBackDelay timer starts, extend the NAV on the
3262 // aux PHY link on which the main PHY is operating by the timer duration
3263 // plus a channel switch delay, so that the timer expires and the main PHY
3264 // returns to the preferred link. If nSlotsAlert > 0, the timer duration is
3265 // extended by the expected channel access when the main PHY switch ends.
3266 Simulator::Schedule(startTimerDelay, [=, this]() {
3267 auto auxLinkId = m_staMacs[0]->GetLinkForPhy(mainPhy);
3269 auxLinkId.has_value(),
3270 true,
3271 "Main PHY should be operating on a link before timer expires");
3272 auto timerDuration = switchMainPhyBackDelay.Get();
3273 if (m_nSlotsLeftAlert > 0)
3274 {
3275 timerDuration += (m_staMacs[0]
3276 ->GetChannelAccessManager(*auxLinkId)
3277 ->GetBackoffEndFor(acBe) -
3278 Simulator::Now());
3279 }
3280 m_staMacs[0]
3281 ->GetChannelAccessManager(*auxLinkId)
3282 ->NotifyNavStartNow(timerDuration +
3283 mainPhy->GetChannelSwitchDelay());
3284
3285 // check that the SwitchMainPhyBackDelay timer expires and the main PHY
3286 // returns to the preferred link
3287 Simulator::Schedule(timerDuration + TimeStep(1), [=, this]() {
3289 "TxopNotGainedOnAuxPhyLink",
3290 std::nullopt,
3292 false);
3293 });
3294 });
3295 }
3296
3297 // events to be scheduled when main PHY finishes switching to auxiliary link
3298 Simulator::Schedule(mainPhy->GetDelayUntilIdle(), [=, this]() {
3299 auto auxLinkId = m_staMacs[0]->GetLinkForPhy(mainPhy);
3300 NS_TEST_ASSERT_MSG_EQ(auxLinkId.has_value(),
3301 true,
3302 "Main PHY should have completed switching");
3303 // update backoff on the auxiliary link on which main PHY is operating
3304 auto cam = m_staMacs[0]->GetChannelAccessManager(*auxLinkId);
3305 cam->NeedBackoffUponAccess(acBe, true, true);
3306 const auto usedAuxPhyCca =
3307 (m_useAuxPhyCca || m_auxPhyChannelWidth >= m_channelWidth) &&
3308 (m_nSlotsLeftAlert == 0 ||
3309 cam->GetBackoffEndFor(acBe) <= Simulator::Now());
3310 m_5thQosFrameExpWidth =
3311 usedAuxPhyCca ? m_auxPhyChannelWidth : m_channelWidth;
3312 // record the time the transmission of the QoS data frames must have
3313 // started: (a PIFS after) end of channel switch, if the backoff counter
3314 // on the auxiliary link is null and UseAuxPhyCca is true (false); when
3315 // the backoff expires, otherwise
3316 if (auto slots = acBe->GetBackoffSlots(*auxLinkId); slots == 0)
3317 {
3318 m_5thQosFrameTxTime =
3319 Simulator::Now() + (m_useAuxPhyCca ? Time{0} : mainPhy->GetPifs());
3320 }
3321 else
3322 {
3323 m_5thQosFrameTxTime = cam->GetBackoffEndFor(acBe);
3324 }
3325 });
3326 });
3327 });
3328 });
3329
3330 // generate data packets for another UL data frame
3331 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
3332 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
3333 break;
3334 }
3335}
3336
3337void
3339 const WifiTxVector& txVector,
3340 uint8_t linkId)
3341{
3343 {
3344 // this function only considers RTS frames sent after the first QoS data frame
3345 return;
3346 }
3347
3348 if (linkId != m_mainPhyId)
3349 {
3350 if (m_countRtsframes > 0 && !m_corruptCts.has_value())
3351 {
3352 // we get here for the frame exchange in which the CTS response must be corrupted.
3353 // Install post reception error model on the STA affiliated with the EMLSR client that
3354 // is transmitting this RTS frame
3356 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
3357 m_corruptCts = true;
3358 }
3359
3360 return;
3361 }
3362
3363 // we get here for RTS frames sent by the main PHY while the MediumSyncDelay timer is running
3365
3367 m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId)->GetChannelWidth(),
3368 "RTS sent by main PHY on an unexpected width");
3369
3370 // corrupt reception at AP MLD
3371 NS_LOG_INFO("CORRUPTED");
3372 m_errorModel->SetList({mpdu->GetPacket()->GetUid()});
3373}
3374
3375void
3377 const WifiTxVector& txVector,
3378 uint8_t linkId)
3379{
3381 {
3382 // this function only considers CTS frames sent after the first QoS data frame
3383 return;
3384 }
3385
3386 auto txDuration = WifiPhy::CalculateTxDuration(mpdu->GetSize(),
3387 txVector,
3388 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
3389 const auto doCorruptCts = m_corruptCts.has_value() && *m_corruptCts;
3390
3391 if (linkId != m_staMacs[0]->GetLinkForPhy(m_mainPhyId) && linkId != m_nonEmlsrLink &&
3392 mpdu->GetHeader().GetAddr1() == m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress())
3393 {
3394 // this is a CTS sent to an aux PHY starting an UL TXOP. Given that aux PHYs do not
3395 // switch channel, they are put in sleep mode when the main PHY starts operating on their
3396 // link, which coincides with the end of CTS plus two propagation delays
3397 const auto auxPhy = m_staMacs[0]->GetWifiPhy(linkId);
3398 const auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
3399 Simulator::Schedule(txDuration, [=, this]() {
3400 // when CTS ends, the main PHY is still switching and the aux PHY is not yet sleeping
3401 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
3402 true,
3403 "Expecting the main PHY to be switching link");
3404 NS_TEST_EXPECT_MSG_EQ(auxPhy->IsStateSleep(),
3405 false,
3406 "Aux PHY on link " << +linkId << " already in sleep mode");
3407 // when CTS is sent, the main PHY may have already started switching, thus we may not
3408 // know which link the main PHY is moving from
3409 CheckMainPhyTraceInfo(0, "UlTxopRtsSentByAuxPhy", std::nullopt, linkId, false);
3410 });
3412 txDuration + MicroSeconds(2 * MAX_PROPAGATION_DELAY_USEC) + TimeStep(1),
3413 [=, this]() {
3414 // aux PHYs are put to sleep if and only if CTS is not corrupted
3415 // (causing the end of the TXOP)
3416 CheckAuxPhysSleepMode(m_staMacs[0], !doCorruptCts);
3417 // if CTS is corrupted, TXOP ends and the main PHY switches back
3418 // to the preferred link
3419 if (doCorruptCts)
3420 {
3421 // check the traced remaining time before calling CheckMainPhyTraceInfo
3422 if (const auto traceInfoIt = m_traceInfo.find(0);
3423 traceInfoIt != m_traceInfo.cend() &&
3424 traceInfoIt->second->GetName() == "TxopEnded")
3425 {
3426 const auto& traceInfo =
3427 static_cast<const EmlsrTxopEndedTrace&>(*traceInfoIt->second);
3428 NS_TEST_EXPECT_MSG_GT(traceInfo.remTime,
3429 Time{0},
3430 "Expected non-zero remaining time because main PHY "
3431 "was switching when TXOP ended");
3432 }
3433
3434 CheckMainPhyTraceInfo(0, "TxopEnded", linkId, m_mainPhyId);
3435 }
3436 });
3437 }
3438
3439 if (doCorruptCts)
3440 {
3441 // corrupt reception at EMLSR client
3442 NS_LOG_INFO("CORRUPTED");
3443 m_errorModel->SetList({mpdu->GetPacket()->GetUid()});
3444 m_corruptCts = false;
3445 }
3446}
3447
3448void
3458
3459void
3461{
3462 if (m_msdMaxNTxops > 0)
3463 {
3467 "Unexpected number of RTS frames sent while the MediumSyncDelay timer is running");
3468 }
3469
3470 auto psduIt = m_txPsdus.cbegin();
3471
3472 // lambda to jump to the next QoS data frame or MU-RTS Trigger Frame or RTS transmitted
3473 // to/by an EMLSR client
3474 auto jumpToQosDataOrMuRts = [&]() {
3475 while (psduIt != m_txPsdus.cend() &&
3476 !psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData() &&
3477 !psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts())
3478 {
3479 auto psdu = psduIt->psduMap.cbegin()->second;
3480 if (psdu->GetHeader(0).IsTrigger())
3481 {
3482 CtrlTriggerHeader trigger;
3483 psdu->GetPayload(0)->PeekHeader(trigger);
3484 if (trigger.IsMuRts())
3485 {
3486 break;
3487 }
3488 }
3489 psduIt++;
3490 }
3491 };
3492
3493 /**
3494 * EMLSR client with EMLSR mode enabled on all links (main PHY ID = 1).
3495 *
3496 * main PHY│
3497 * blocked,│
3498 * aux PHYs││main PHY blocked│
3499 * cannot │
3500 * transmit│
3501 * │ ┌───┐ ┌──┐
3502 * [link 0] │CTS│ │BA│
3503 * ────────────────────────┬───┬┴───┴┬───┬───┬┴──┴─────────────────────────────────────────
3504 * │RTS│ │QoS│QoS│
3505 * └───┘ │ 6 │ 7 │
3506 * └───┴───┘
3507 * gen backoff gen backoff if MediumSyncDelay
3508 * ┌──┐ (also many times) not running timer expired ┌──┐
3509 * [link 1] │BA│ │ if allowed │ │ │BA│
3510 * ─────────┬───┬───┬┴──┴───────────────────────────┬───┬─────┬───┬────┬───┬───┬┴──┴───────
3511 * │QoS│QoS│ │RTS│ ... │RTS│ │QoS│QoS│
3512 * │ 4 │ 5 │ └───┘ └───┘ │ 8 │ 9 │
3513 * └───┴───┘ └───┴───┘
3514 *
3515 * [link 2]
3516 * ───────────────────────────────────────────────────────────────────────────
3517 *
3518 *
3519 *
3520 * EMLSR client with EMLSR mode enabled on links 0 and 1 (main PHY ID = 1).
3521 *
3522 * main PHY │
3523 * and │
3524 * non-EMLSR│
3525 * link │
3526 * blocked,│
3527 * aux PHYs││main PHY blocked│
3528 * cannot │
3529 * transmit│
3530 * │ ┌───┐ ┌──┐
3531 * [link 0] │CTS│ │BA│
3532 * ────────────────────────┬───┬┴───┴┬───┬───┬┴──┴─────────────────────────────────────────
3533 * │RTS│ │QoS│QoS│
3534 * └───┘ │ 8 │ 9 │
3535 * └───┴───┘
3536 * gen backoff gen backoff if MediumSyncDelay
3537 * ┌──┐ (also many times) not running timer expired ┌──┐
3538 * [link 1] │BA│ │ if allowed │ │ │BA│
3539 * ─────────┬───┬───┬┴──┴───────────────────────────┬───┬─────┬───┬────┬───┬───┬┴──┴───────
3540 * │QoS│QoS│ │RTS│ ... │RTS│ │QoS│QoS│
3541 * │ 4 │ 5 │ └───┘ └───┘ │ 10│ 11│
3542 * └───┴───┘ └───┴───┘
3543 * ┌──┐
3544 * [link 2] │BA│
3545 * ──────────┬───┬───┬┴──┴────────────────────────────────────────────────────────────
3546 * │QoS│QoS│
3547 * │ 6 │ 7 │
3548 * └───┴───┘
3549 *
3550 * For both scenarios, after the last frame exchange on the main PHY link, we have the
3551 * following frame exchanges on an EMLSR link where an aux PHY is operating on. After the
3552 * first frame exchange, aux PHYs are configured as non-TX capable. Note that the two frame
3553 * exchanges may occur on distinct auxiliary EMLSR links.
3554 *
3555 * | main PHY || main PHY |
3556 * [ link ] ┌───┐ ┌───┐ ┌──┐|switches to||switches to| ┌──┐
3557 * [0 or 2] │CTS│ │CTS│ │BA│| preferred ||auxiliary |PIFS| │BA│
3558 * ──────┬───┬┴───X────┬───┬┴───┴┬───┬───┬┴──┴──────────────────────────────┬───┬───┬┴──┴───
3559 * │RTS│ │RTS│ │QoS│QoS│ │QoS│QoS│
3560 * └───┘ └───┘ │ X │ Y │ │ Z │ W │
3561 * └───┴───┘ └───┴───┘
3562 * For all EMLSR links scenario, X=10, Y=11, Z=12, W=13
3563 * For the scenario with a non-EMLSR link, X=12, Y=13, Z=14, W=15
3564 */
3565
3566 // jump to the first (non-Beacon) frame transmitted after establishing BA agreements and
3567 // enabling EMLSR mode
3568 while (psduIt != m_txPsdus.cend() &&
3569 (psduIt->startTx < m_firstUlPktsGenTime ||
3570 psduIt->psduMap.cbegin()->second->GetHeader(0).IsBeacon()))
3571 {
3572 ++psduIt;
3573 }
3574
3575 // the first QoS data frame is transmitted by the main PHY without RTS protection as soon
3576 // as transmissions on the link where the main PHY is operating are unblocked (at this
3577 // moment, aux PHYs cannot transmit)
3578 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3579 true,
3580 "First QoS data frame has not been transmitted");
3581 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
3582 true,
3583 "First QoS data frame should be transmitted without protection");
3584 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
3585 +m_mainPhyId,
3586 "First QoS data frame should be transmitted by the main PHY");
3587 NS_TEST_EXPECT_MSG_GT_OR_EQ(psduIt->startTx,
3589 "First QoS data frame sent too early");
3590
3591 auto prevPsduIt = psduIt++;
3592 jumpToQosDataOrMuRts();
3593
3594 if (m_nonEmlsrLink)
3595 {
3596 // an additional data frame is sent concurrently on the non-EMLSR link
3598 (psduIt != m_txPsdus.cend()),
3599 true,
3600 "Expected another QoS data frame sent concurrently with the first frame");
3602 psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
3603 true,
3604 "First data frame on non-EMLSR link should be transmitted without protection");
3605 NS_TEST_EXPECT_MSG_EQ(+psduIt->linkId,
3606 +m_nonEmlsrLink.value(),
3607 "First data frame expected to be transmitted on the non-EMLSR link");
3608 const auto txDuration =
3609 WifiPhy::CalculateTxDuration(prevPsduIt->psduMap,
3610 prevPsduIt->txVector,
3611 m_staMacs[0]->GetWifiPhy(prevPsduIt->phyId)->GetPhyBand());
3612 NS_TEST_EXPECT_MSG_LT(psduIt->startTx,
3613 prevPsduIt->startTx + txDuration,
3614 "First data frame on the non-EMLSR link not sent concurrently");
3615 psduIt++;
3616 jumpToQosDataOrMuRts();
3617 }
3618
3619 // the second QoS data frame is transmitted by the main PHY after that the aux PHY has
3620 // obtained a TXOP and sent an RTS
3621 // RTS
3622 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3623 true,
3624 "RTS before second QoS data frame has not been transmitted");
3625 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(),
3626 true,
3627 "Second QoS data frame should be transmitted with protection");
3629 +psduIt->phyId,
3630 +m_mainPhyId,
3631 "RTS before second QoS data frame should not be transmitted by the main PHY");
3632 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3634 "RTS before second data frame transmitted on an unexpected width");
3635 psduIt++;
3636 // CTS
3637 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3638 true,
3639 "CTS before second QoS data frame has not been transmitted");
3640 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(),
3641 true,
3642 "CTS before second QoS data frame has not been transmitted");
3643 psduIt++;
3644 // QoS Data
3645 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3646 true,
3647 "Second QoS data frame has not been transmitted");
3648 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
3649 true,
3650 "Second QoS data frame has not been transmitted");
3651 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
3652 +m_mainPhyId,
3653 "Second QoS data frame should be transmitted by the main PHY");
3654 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3656 "Second data frame not transmitted on the same width as RTS");
3657
3658 bool moreQosDataFound = false;
3659
3660 while (++psduIt != m_txPsdus.cend())
3661 {
3662 jumpToQosDataOrMuRts();
3663 if (psduIt != m_txPsdus.cend() &&
3664 psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData())
3665 {
3666 moreQosDataFound = true;
3667
3668 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
3669 +m_mainPhyId,
3670 "Third QoS data frame should be transmitted by the main PHY");
3671 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3672 m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId)->GetChannelWidth(),
3673 "Expecting TX width of third data frame to equal the channel "
3674 "width used by the main PHY");
3676 psduIt->startTx,
3678 "Third QoS data frame sent before MediumSyncDelay timer expired");
3679
3680 break;
3681 }
3682 }
3683
3684 NS_TEST_EXPECT_MSG_EQ(moreQosDataFound,
3685 true,
3686 "Third QoS data frame transmitted by the main PHY not found");
3687
3688 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()), true, "Expected more frames");
3689 ++psduIt;
3690 jumpToQosDataOrMuRts();
3691
3692 // the first attempt at transmitting the fourth QoS data frame fails because CTS is corrupted
3693 // RTS
3694 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3695 true,
3696 "RTS before fourth QoS data frame has not been transmitted");
3697 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(),
3698 true,
3699 "Fourth QoS data frame should be transmitted with protection");
3701 +psduIt->phyId,
3702 +m_mainPhyId,
3703 "RTS before fourth QoS data frame should not be transmitted by the main PHY");
3704 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3706 "RTS before fourth data frame transmitted on an unexpected width");
3707 psduIt++;
3708 // CTS
3709 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3710 true,
3711 "CTS before fourth QoS data frame has not been transmitted");
3712 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(),
3713 true,
3714 "CTS before fourth QoS data frame has not been transmitted");
3715 psduIt++;
3716 jumpToQosDataOrMuRts();
3717
3718 // the fourth QoS data frame is transmitted by an aux PHY after that the aux PHY has
3719 // obtained a TXOP and sent an RTS
3720 // RTS
3721 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3722 true,
3723 "RTS before fourth QoS data frame has not been transmitted");
3724 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(),
3725 true,
3726 "Fourth QoS data frame should be transmitted with protection");
3728 +psduIt->phyId,
3729 +m_mainPhyId,
3730 "RTS before fourth QoS data frame should not be transmitted by the main PHY");
3731 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3733 "RTS before fourth data frame transmitted on an unexpected width");
3734 psduIt++;
3735 // CTS
3736 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3737 true,
3738 "CTS before fourth QoS data frame has not been transmitted");
3739 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(),
3740 true,
3741 "CTS before fourth QoS data frame has not been transmitted");
3742 psduIt++;
3743 // QoS Data
3744 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3745 true,
3746 "Fourth QoS data frame has not been transmitted");
3747 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
3748 true,
3749 "Fourth QoS data frame has not been transmitted");
3750 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
3751 +m_mainPhyId,
3752 "Fourth QoS data frame should be transmitted by the main PHY");
3753 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3755 "Fourth data frame not transmitted on the same width as RTS");
3756
3757 auto fourthLinkId = psduIt->linkId;
3758
3759 psduIt++;
3760 jumpToQosDataOrMuRts();
3761
3762 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()), true, "Expected more frames");
3763 // Do not check the start transmission time if a backoff is generated even when no
3764 // transmission is done (if the backoff expires while the main PHY is switching, a new
3765 // backoff is generated and, before this backoff expires, the main PHY may be requested
3766 // to switch to another auxiliary link; this may happen multiple times...)
3768 {
3769 NS_TEST_EXPECT_MSG_LT_OR_EQ(psduIt->startTx,
3771 "Fifth data frame transmitted too late");
3772 }
3773
3774 // the fifth QoS data frame is transmitted by the main PHY on an auxiliary link because
3775 // the aux PHY is not TX capable. The QoS data frame is protected by RTS if it is transmitted
3776 // on a different link than the previous one (because the MediumSyncDelay timer is running)
3777 if (psduIt->linkId != fourthLinkId)
3778 {
3779 // RTS
3780 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(),
3781 true,
3782 "Fifth QoS data frame should be transmitted with protection");
3784 +psduIt->phyId,
3785 +m_mainPhyId,
3786 "RTS before fifth QoS data frame should be transmitted by the main PHY");
3787 psduIt++;
3788 // CTS
3789 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3790 true,
3791 "CTS before fifth QoS data frame has not been transmitted");
3792 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(),
3793 true,
3794 "CTS before fifth QoS data frame has not been transmitted");
3795 psduIt++;
3796 }
3797
3798 // QoS Data
3799 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
3800 true,
3801 "Fifth QoS data frame has not been transmitted");
3802 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
3803 true,
3804 "Fifth QoS data frame has not been transmitted");
3805 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
3806 +m_mainPhyId,
3807 "Fifth QoS data frame should be transmitted by the main PHY");
3808 NS_TEST_EXPECT_MSG_NE(+psduIt->linkId,
3809 +m_mainPhyId,
3810 "Fifth QoS data frame should be transmitted on an auxiliary link");
3811 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
3813 "Fifth data frame not transmitted on the correct channel width");
3814}
3815
3817 : EmlsrOperationsTestBase("Check UL OFDMA operations with an EMLSR client"),
3818 m_enableBsrp(enableBsrp),
3819 m_txPsdusPos(0),
3820 m_startAccessReq(0)
3821{
3822 m_linksToEnableEmlsrOn = {0, 1, 2};
3823 m_nEmlsrStations = 1;
3825 m_establishBaDl = false;
3826 m_establishBaUl = true;
3827 m_mainPhyId = 1;
3828 m_duration = Seconds(1);
3829}
3830
3831void
3833{
3834 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(m_transitionDelay.at(0)));
3835
3837
3838 m_apMac->GetQosTxop(AC_BE)->SetTxopLimits(
3839 {MicroSeconds(3200), MicroSeconds(3200), MicroSeconds(3200)});
3840
3841 auto muScheduler = CreateObjectWithAttributes<RrMultiUserScheduler>("EnableUlOfdma",
3842 BooleanValue(true),
3843 "EnableBsrp",
3845 m_apMac->AggregateObject(muScheduler);
3846}
3847
3848void
3850 uint8_t phyId,
3851 WifiConstPsduMap psduMap,
3852 WifiTxVector txVector,
3853 double txPowerW)
3854{
3855 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
3856 auto linkId = m_txPsdus.back().linkId;
3857
3858 auto psdu = psduMap.begin()->second;
3859
3860 switch (psdu->GetHeader(0).GetType())
3861 {
3864 {
3865 // this is the first Trigger Frame sent after the AP requested channel access
3866 // through the Multi-user scheduler and it is an ICF for the EMLSR client
3867 m_txPsdusPos = m_txPsdus.size() - 1;
3868 auto txDuration = WifiPhy::CalculateTxDuration(psduMap,
3869 txVector,
3870 mac->GetWifiPhy(linkId)->GetPhyBand());
3871 NS_LOG_INFO("This is the first Trigger Frame\n");
3872 // once the Trigger Frame is received by the EMLSR client, make the client application
3873 // on the EMLSR client generate two packets. These packets will be sent via UL OFDMA
3874 // because the EMLSR client has blocked transmissions on other links when receiving
3875 // this Trigger Frame, hence it will not try to get access on other links via EDCA
3877 txDuration + MicroSeconds(1), // to account for propagation delay
3878 [=, this]() {
3879 for (const auto id : m_staMacs[0]->GetLinkIds())
3880 {
3882 m_staMacs[0]->GetFrameExchangeManager(id));
3884 ehtFem->UsingOtherEmlsrLink(),
3885 (id != linkId),
3886 "Link " << +id << " was" << (id == linkId ? " not" : "")
3887 << " expected to be blocked on EMLSR client at time "
3888 << Simulator::Now().As(Time::NS));
3889 }
3890 NS_LOG_INFO("Generate two packets\n");
3891 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
3892 GetApplication(UPLINK, 0, 2, 100));
3893 });
3894 }
3895 break;
3896
3899 {
3900 CtrlBAckResponseHeader blockAck;
3901 psdu->GetPayload(0)->PeekHeader(blockAck);
3902 if (blockAck.IsMultiSta())
3903 {
3904 auto txDuration =
3906 txVector,
3907 mac->GetWifiPhy(linkId)->GetPhyBand());
3908 Simulator::Stop(txDuration + MicroSeconds(1));
3909 }
3910 }
3911 break;
3912
3913 default:;
3914 }
3915
3916 if (psdu->GetHeader(0).IsCfEnd())
3917 {
3918 // we do not check CF-End frames
3919 m_txPsdus.pop_back();
3920 }
3921}
3922
3923void
3933
3934void
3936{
3937 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
3938 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "No MU scheduler installed on AP MLD");
3939
3940 NS_LOG_INFO("Setting Access Request interval");
3941
3942 const auto interval = MilliSeconds(50);
3943 muScheduler->SetAccessReqInterval(interval);
3944 m_startAccessReq = Simulator::Now() + interval;
3945}
3946
3947void
3949{
3950 /**
3951 * Sending BSRP TF disabled.
3952 *
3953 * The figure assumes that link 0 is used to send the first Trigger Frame after that the
3954 * AP MLD requests channel access through the Multi-user scheduler. The first Trigger Frame
3955 * is MU-RTS because EMLSR client needs an ICF; the other Trigger Frames are Basic TFs and
3956 * do not solicit the EMLSR client.
3957 * ┌─────┐ ┌─────┐ ┌──────┐
3958 * │ MU │ │Basic│ │Multi-│
3959 * [link 0] │ RTS │ │ TF │ │STA BA│
3960 * ───────────┴─────┴┬───┬┴─────┴┬────────┬─┴──────┴───────────────
3961 * │CTS│ │QoS Null│
3962 * ├───┤ ├────────┤
3963 * │CTS│ │QoS Data│
3964 * └───┘ └────────┘
3965 *
3966 * ┌─────┐
3967 * │Basic│
3968 * [link 1] │ TF │
3969 * ─────────────┴─────┴┬────┬──────────────────────────────────────
3970 * │QoS │
3971 * │Null│
3972 * └────┘
3973 *
3974 * ┌─────┐
3975 * │Basic│
3976 * [link 2] │ TF │
3977 * ─────────────┴─────┴┬────┬──────────────────────────────────────
3978 * │QoS │
3979 * │Null│
3980 * └────┘
3981 *
3982 * Sending BSRP TF enabled.
3983 *
3984 * The figure assumes that link 0 is used to send the first Trigger Frame after that the
3985 * AP MLD requests channel access through the Multi-user scheduler. The first Trigger Frames
3986 * are all BSRP Trigger Frames, but only the first one solicits the EMLSR client, too.
3987 * ┌─────┐ ┌─────┐ ┌──────┐
3988 * │BSRP │ │Basic│ │Multi-│
3989 * [link 0] │ TF │ │ TF │ │STA BA│
3990 * ───────────┴─────┴┬────────┬┴─────┴┬────────┬─┴──────┴──────────
3991 * │QoS Null│ │QoS Data│
3992 * ├────────┤ └────────┘
3993 * │QoS Null│
3994 * └────────┘
3995 *
3996 * ┌─────┐
3997 * │BSRP │
3998 * [link 1] │ TF │
3999 * ─────────────┴─────┴┬────┬──────────────────────────────────────
4000 * │QoS │
4001 * │Null│
4002 * └────┘
4003 *
4004 * ┌─────┐
4005 * │BSRP │
4006 * [link 2] │ TF │
4007 * ─────────────┴─────┴┬────┬──────────────────────────────────────
4008 * │QoS │
4009 * │Null│
4010 * └────┘
4011 */
4012
4013 NS_TEST_ASSERT_MSG_GT(m_txPsdusPos, 0, "First Trigger Frame not detected");
4014
4015 // Check the Trigger Frames (one per link) after requesting channel access
4016 auto index = m_txPsdusPos;
4017 const auto firstLinkId = m_txPsdus[m_txPsdusPos].linkId;
4018 for (; index < m_txPsdusPos + 3; ++index)
4019 {
4020 NS_TEST_ASSERT_MSG_EQ(m_txPsdus[index].psduMap.cbegin()->second->GetHeader(0).IsTrigger(),
4021 true,
4022 "Expected a Trigger Frame");
4023 CtrlTriggerHeader trigger;
4024 m_txPsdus[index].psduMap.cbegin()->second->GetPayload(0)->PeekHeader(trigger);
4025
4026 TriggerFrameType triggerType =
4027 m_enableBsrp ? TriggerFrameType::BSRP_TRIGGER
4028 : (index == m_txPsdusPos ? TriggerFrameType::MU_RTS_TRIGGER
4029 : TriggerFrameType::BASIC_TRIGGER);
4030 NS_TEST_EXPECT_MSG_EQ(+static_cast<uint8_t>(trigger.GetType()),
4031 +static_cast<uint8_t>(triggerType),
4032 "Unexpected Trigger Frame type on link " << +m_txPsdus[index].linkId);
4033
4034 // only the first TF solicits the EMLSR client and the non-AP MLD
4036 trigger.GetNUserInfoFields(),
4037 (index == m_txPsdusPos ? 2 : 1),
4038 "Unexpected number of User Info fields for Trigger Frame, index=" << index);
4039 }
4040
4041 auto startIndex = index;
4042 std::size_t ctsCount = 0;
4043 std::size_t qosNullCount = 0;
4044 // Check responses to Trigger Frames
4045 for (; index < startIndex + 4; ++index)
4046 {
4047 const auto& hdr = m_txPsdus[index].psduMap.cbegin()->second->GetHeader(0);
4048
4049 if (hdr.IsCts())
4050 {
4051 ++ctsCount;
4052 continue;
4053 }
4054
4055 if (hdr.IsQosData() && !hdr.HasData())
4056 {
4057 ++qosNullCount;
4058 // if BSRP is enabled, the QoS Null frame sent by the EMLSR client in response to the
4059 // first BSRP TF reports a non-null buffer status
4060 if (m_enableBsrp &&
4061 hdr.GetAddr2() == m_staMacs[0]->GetFrameExchangeManager(firstLinkId)->GetAddress())
4062 {
4063 NS_TEST_EXPECT_MSG_GT(+hdr.GetQosQueueSize(), 0, "Unexpected buffer size");
4064 }
4065 else
4066 {
4067 NS_TEST_EXPECT_MSG_EQ(+hdr.GetQosQueueSize(), 0, "Unexpected buffer size");
4068 }
4069 continue;
4070 }
4071 }
4072 NS_TEST_EXPECT_MSG_EQ(ctsCount, (m_enableBsrp ? 0 : 2), "Unexpected number of CTS frames");
4073 NS_TEST_EXPECT_MSG_EQ(qosNullCount,
4074 (m_enableBsrp ? 4 : 2),
4075 "Unexpected number of QoS Null frames");
4076
4077 // we expect only one Basic Trigger Frame (sent on the same link as the first Trigger Frame),
4078 // because the buffer status reported on the other links by the non-EMLSR client is zero
4079 NS_TEST_ASSERT_MSG_EQ(m_txPsdus[index].psduMap.cbegin()->second->GetHeader(0).IsTrigger(),
4080 true,
4081 "Expected a Trigger Frame");
4082 NS_TEST_EXPECT_MSG_EQ(+m_txPsdus[index].linkId,
4083 +firstLinkId,
4084 "Unexpected link ID for Basic TF");
4085 CtrlTriggerHeader trigger;
4086 m_txPsdus[index].psduMap.cbegin()->second->GetPayload(0)->PeekHeader(trigger);
4087
4088 NS_TEST_EXPECT_MSG_EQ(+static_cast<uint8_t>(trigger.GetType()),
4089 +static_cast<uint8_t>(TriggerFrameType::BASIC_TRIGGER),
4090 "Unexpected Trigger Frame type");
4091
4092 // when BSRP TF is enabled, the non-EMLSR client has already communicated a buffer status of
4093 // zero, so it is not solicited by the AP through the Basic Trigger Frame. Otherwise, it is
4094 // solicited because buffer status was not known when the BSRP TF was prepared (before sending
4095 // MU-RTS)
4097 (m_enableBsrp ? 1 : 2),
4098 "Unexpected number of User Info fields for Basic Trigger Frame");
4099
4100 // Response(s) to the Basic Trigger Frame
4101 startIndex = ++index;
4102 for (; index < startIndex + (m_enableBsrp ? 1 : 2); ++index)
4103 {
4104 const auto& hdr = m_txPsdus[index].psduMap.cbegin()->second->GetHeader(0);
4105
4106 NS_TEST_EXPECT_MSG_EQ(hdr.IsQosData(), true, "Expected a QoS frame");
4107
4108 // EMLSR client sends a QoS Data frame, non-EMLSR client sends a QoS Null frame
4110 hdr.HasData(),
4111 (hdr.GetAddr2() == m_staMacs[0]->GetFrameExchangeManager(firstLinkId)->GetAddress()),
4112 "Unexpected type of QoS data frame");
4113
4114 if (hdr.HasData())
4115 {
4116 NS_TEST_EXPECT_MSG_EQ(m_txPsdus[index].txVector.IsUlMu(),
4117 true,
4118 "QoS Data frame should be sent in a TB PPDU");
4119 }
4120 }
4121
4122 // Finally, the AP MLD sends a Multi-STA BlockAck
4123 NS_TEST_EXPECT_MSG_EQ(m_txPsdus[index].psduMap.cbegin()->second->GetHeader(0).IsBlockAck(),
4124 true,
4125 "Expected a BlockAck frame");
4126 CtrlBAckResponseHeader blockAck;
4127 m_txPsdus[index].psduMap.cbegin()->second->GetPayload(0)->PeekHeader(blockAck);
4128 NS_TEST_EXPECT_MSG_EQ(blockAck.IsMultiSta(), true, "Expected a Multi-STA BlockAck");
4129}
4130
4133 std::string("Check EMLSR link switching (switchAuxPhy=") +
4134 std::to_string(params.switchAuxPhy) + ", resetCamStateAndInterruptSwitch=" +
4135 std::to_string(params.resetCamStateAndInterruptSwitch) +
4136 ", auxPhyMaxChWidth=" + std::to_string(params.auxPhyMaxChWidth) + "MHz )"),
4137 m_switchAuxPhy(params.switchAuxPhy),
4138 m_resetCamStateAndInterruptSwitch(params.resetCamStateAndInterruptSwitch),
4139 m_auxPhyMaxChWidth(params.auxPhyMaxChWidth),
4140 m_countQoSframes(0),
4141 m_countIcfFrames(0),
4142 m_countRtsFrames(0),
4143 m_txPsdusPos(0)
4144{
4145 m_nEmlsrStations = 1;
4147 m_linksToEnableEmlsrOn = {0, 1, 2}; // enable EMLSR on all links right after association
4148 m_mainPhyId = 1;
4149 m_establishBaDl = true;
4150 m_duration = Seconds(1);
4151 // when aux PHYs do not switch link, the main PHY switches back to its previous link after
4152 // a TXOP, hence the transition delay must exceed the channel switch delay (default: 250us)
4154}
4155
4156void
4158 uint8_t phyId,
4159 WifiConstPsduMap psduMap,
4160 WifiTxVector txVector,
4161 double txPowerW)
4162{
4163 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
4164 auto linkId = m_txPsdus.back().linkId;
4165
4166 auto psdu = psduMap.begin()->second;
4167 auto nodeId = mac->GetDevice()->GetNode()->GetId();
4168
4169 switch (psdu->GetHeader(0).GetType())
4170 {
4172 NS_ASSERT_MSG(nodeId > 0, "APs do not send AssocReq frames");
4173 NS_TEST_EXPECT_MSG_EQ(+linkId, +m_mainPhyId, "AssocReq not sent by the main PHY");
4174 break;
4175
4176 case WIFI_MAC_MGT_ACTION: {
4177 auto [category, action] = WifiActionHeader::Peek(psdu->GetPayload(0));
4178
4179 if (nodeId == 1 && category == WifiActionHeader::PROTECTED_EHT &&
4180 action.protectedEhtAction ==
4182 {
4183 // the EMLSR client is starting the transmission of the EML OMN frame;
4184 // temporarily block transmissions of QoS data frames from the AP MLD to the
4185 // non-AP MLD on all the links but the one used for ML setup, so that we know
4186 // that the first QoS data frame is sent on the link of the main PHY
4187 std::set<uint8_t> linksToBlock;
4188 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
4189 {
4190 if (id != m_mainPhyId)
4191 {
4192 linksToBlock.insert(id);
4193 }
4194 }
4195 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4196 AC_BE,
4198 m_staMacs[0]->GetAddress(),
4199 m_apMac->GetAddress(),
4200 {0},
4201 linksToBlock);
4202 }
4203 }
4204 break;
4205
4207 CheckInitialControlFrame(psduMap, txVector, linkId);
4208 break;
4209
4210 case WIFI_MAC_QOSDATA:
4211 CheckQosFrames(psduMap, txVector, linkId);
4212 break;
4213
4214 case WIFI_MAC_CTL_RTS:
4215 CheckRtsFrame(psduMap, txVector, linkId);
4216 break;
4217
4218 default:;
4219 }
4220}
4221
4222void
4224{
4225 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(m_switchAuxPhy));
4226 Config::SetDefault("ns3::EmlsrManager::ResetCamState",
4228 Config::SetDefault("ns3::AdvancedEmlsrManager::InterruptSwitch",
4230 Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth", UintegerValue(m_auxPhyMaxChWidth));
4231 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(45)));
4232
4234
4236 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); ++linkId)
4237 {
4238 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
4239 }
4240
4241 // use channels of different widths
4242 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0]})
4243 {
4244 mac->GetWifiPhy(0)->SetOperatingChannel(
4246 mac->GetWifiPhy(1)->SetOperatingChannel(
4248 mac->GetWifiPhy(2)->SetOperatingChannel(
4250 }
4251}
4252
4253void
4263
4264void
4266 const WifiTxVector& txVector,
4267 uint8_t linkId)
4268{
4270
4271 switch (m_countQoSframes)
4272 {
4273 case 1:
4274 // unblock transmissions on all links
4275 m_apMac->GetMacQueueScheduler()->UnblockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4276 AC_BE,
4278 m_staMacs[0]->GetAddress(),
4279 m_apMac->GetAddress(),
4280 {0},
4281 {0, 1, 2});
4282 // block transmissions on the link used for ML setup
4283 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4284 AC_BE,
4286 m_staMacs[0]->GetAddress(),
4287 m_apMac->GetAddress(),
4288 {0},
4289 {m_mainPhyId});
4290 // generate a new data packet, which will be sent on a link other than the one
4291 // used for ML setup, hence triggering a link switching on the EMLSR client
4292 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 0, 2, 1000));
4293 break;
4294 case 2:
4295 // block transmission on the link used to send this QoS data frame
4296 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4297 AC_BE,
4299 m_staMacs[0]->GetAddress(),
4300 m_apMac->GetAddress(),
4301 {0},
4302 {linkId});
4303 // generate a new data packet, which will be sent on the link that has not been used
4304 // so far, hence triggering another link switching on the EMLSR client
4305 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 0, 2, 1000));
4306 break;
4307 case 3:
4308 // block transmission on the link used to send this QoS data frame
4309 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4310 AC_BE,
4312 m_staMacs[0]->GetAddress(),
4313 m_apMac->GetAddress(),
4314 {0},
4315 {linkId});
4316 // unblock transmissions on the link used for ML setup
4317 m_apMac->GetMacQueueScheduler()->UnblockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4318 AC_BE,
4320 m_staMacs[0]->GetAddress(),
4321 m_apMac->GetAddress(),
4322 {0},
4323 {m_mainPhyId});
4324 // generate a new data packet, which will be sent again on the link used for ML setup,
4325 // hence triggering yet another link switching on the EMLSR client
4326 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 0, 2, 1000));
4327 break;
4328 case 4:
4329 // block transmissions on all links at non-AP MLD side
4330 m_staMacs[0]->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4331 AC_BE,
4333 m_apMac->GetAddress(),
4334 m_staMacs[0]->GetAddress(),
4335 {0},
4336 {0, 1, 2});
4337 // unblock transmissions on the link used for ML setup (non-AP MLD side)
4338 m_staMacs[0]->GetMacQueueScheduler()->UnblockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4339 AC_BE,
4341 m_apMac->GetAddress(),
4342 m_staMacs[0]->GetAddress(),
4343 {0},
4344 {m_mainPhyId});
4345 // trigger establishment of BA agreement with AP as recipient
4346 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 4, 1000));
4347 break;
4348 case 5:
4349 // unblock transmissions on all links at non-AP MLD side
4350 m_staMacs[0]->GetMacQueueScheduler()->UnblockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4351 AC_BE,
4353 m_apMac->GetAddress(),
4354 m_staMacs[0]->GetAddress(),
4355 {0},
4356 {0, 1, 2});
4357 // block transmissions on the link used for ML setup (non-AP MLD side)
4358 m_staMacs[0]->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4359 AC_BE,
4361 m_apMac->GetAddress(),
4362 m_staMacs[0]->GetAddress(),
4363 {0},
4364 {m_mainPhyId});
4365 // generate a new data packet, which will be sent on a link other than the one
4366 // used for ML setup, hence triggering a link switching on the EMLSR client
4367 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
4368 break;
4369 }
4370}
4371
4372/**
4373 * AUX PHY switching enabled (X = channel switch delay)
4374 *
4375 * |--------- aux PHY A ---------|------ main PHY ------|-------------- aux PHY B -------------
4376 * ┌───┐ ┌───┐
4377 * │ICF│ │QoS│
4378 * ──────────────────────────┴───┴┬───┬┴───┴┬──┬────────────────────────────────────────────────
4379 * [link 0] │CTS│ │BA│
4380 * └───┘ └──┘
4381 *
4382 *
4383 * |--------- main PHY ----------|------------------ aux PHY A ----------------|--- main PHY ---
4384 * ┌───┐ ┌───┐ ┌───┐ ┌───┐
4385 * │ICF│ │QoS│ │ICF│ │QoS│
4386 * ───┴───┴┬───┬┴───┴┬──┬──────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬──
4387 * [link 1]│CTS│ │BA│ │CTS│ │BA│
4388 * └───┘ └──┘ └───┘ └──┘
4389 *
4390 *
4391 * |--------------------- aux PHY B --------------------|------ main PHY ------|-- aux PHY A ---
4392 * ┌───┐ ┌───┐
4393 * │ICF│ │QoS│
4394 * ─────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬─────────────────────────
4395 * [link 2] │CTS│ │BA│
4396 * └───┘ └──┘
4397 *
4398 * ... continued ...
4399 *
4400 * |----------------------------------------- aux PHY B ---------------------------------------
4401 * ─────────────────────────────────────────────────────────────────────────────────────────────
4402 * [link 0]
4403 *
4404 * |--------- main PHY ----------|X|X|------------------------ aux PHY A ----------------------
4405 * ┌───┐
4406 * │ACK│
4407 * ──────────┬───┬┴───┴────────────────────────────────────────────────────────────────────────
4408 * [link 1] │QoS│
4409 * └───┘
4410 *
4411 * |-------- aux PHY A ----------|X|---------------------- main PHY ---------------------------
4412 * ┌──┐
4413 * │BA│
4414 * ────────────────────────┬───X──────┬───┬┴──┴────────────────────────────────────────────────
4415 * [link 2] │RTS│ │QoS│
4416 * └───┘ └───┘
4417 ************************************************************************************************
4418 *
4419 * AUX PHY switching disabled (X = channel switch delay)
4420 *
4421 * |------------------------------------------ aux PHY A ---------------------------------------
4422 * |-- main PHY --|X|
4423 * ┌───┐ ┌───┐
4424 * │ICF│ │QoS│
4425 * ──────────────────────────┴───┴┬───┬┴───┴┬──┬────────────────────────────────────────────────
4426 * [link 0] │CTS│ │BA│
4427 * └───┘ └──┘
4428 *
4429 * |-main|
4430 * |--------- main PHY ----------| |-PHY-| |------ main PHY ------
4431 * ┌───┐ ┌───┐ ┌───┐ ┌───┐
4432 * │ICF│ │QoS│ │ICF│ │QoS│
4433 * ───┴───┴┬───┬┴───┴┬──┬──────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬──
4434 * [link 1]│CTS│ │BA│ │CTS│ │BA│
4435 * └───┘ └──┘ └───┘ └──┘
4436 *
4437 *
4438 * |------------------------------------------ aux PHY B ---------------------------------------
4439 * |-- main PHY --|X|
4440 * ┌───┐ ┌───┐
4441 * │ICF│ │QoS│
4442 * ─────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬─────────────────────────
4443 * [link 2] │CTS│ │BA│
4444 * └───┘ └──┘
4445 *
4446 * ... continued ...
4447 *
4448 * |----------------------------------------- aux PHY A ---------------------------------------
4449 * ─────────────────────────────────────────────────────────────────────────────────────────────
4450 * [link 0]
4451 *
4452 * |-------- main PHY --------| |--- main PHY ---|
4453 * ┌───┐
4454 * │ACK│
4455 * ──────────┬───┬┴───┴────────────────────────────────────────────────────────────────────────
4456 * [link 1] │QoS│
4457 * └───┘
4458 *
4459 * |------------------------------------------ aux PHY B --------------------------------------
4460 * |X||X| |X|-------------- main PHY --------------
4461 * ┌───┐ ┌──┐
4462 * │CTS│ │BA│
4463 * ────────────────────────┬───X───────────────┬───┬┴───┴┬───┬┴──┴─────────────────────────────
4464 * [link 2] │RTS│ │RTS│ │QoS│
4465 * └───┘ └───┘ └───┘
4466 *
4467 */
4468
4469void
4471 const WifiTxVector& txVector,
4472 uint8_t linkId)
4473{
4474 if (++m_countIcfFrames == 1)
4475 {
4476 m_txPsdusPos = m_txPsdus.size() - 1;
4477 }
4478
4479 // the first ICF is sent to protect ADDBA_REQ for DL BA agreement, then one ICF is sent before
4480 // each of the 4 DL QoS Data frames; finally, another ICF is sent before the ADDBA_RESP for UL
4481 // BA agreement. Hence, at any time the number of ICF sent is always greater than or equal to
4482 // the number of QoS data frames sent.
4483 NS_TEST_EXPECT_MSG_GT_OR_EQ(m_countIcfFrames, m_countQoSframes, "Unexpected number of ICFs");
4484
4485 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
4486 auto phyRecvIcf = m_staMacs[0]->GetWifiPhy(linkId); // PHY receiving the ICF
4487
4488 auto currMainPhyLinkId = m_staMacs[0]->GetLinkForPhy(mainPhy);
4489 NS_TEST_ASSERT_MSG_EQ(currMainPhyLinkId.has_value(),
4490 true,
4491 "Didn't find the link on which the Main PHY is operating");
4492 NS_TEST_ASSERT_MSG_NE(phyRecvIcf,
4493 nullptr,
4494 "No PHY on the link where ICF " << m_countQoSframes << " was sent");
4495
4496 if (phyRecvIcf != mainPhy)
4497 {
4499 phyRecvIcf->GetChannelWidth(),
4501 "Aux PHY that received ICF "
4502 << m_countQoSframes << " is operating on a channel whose width exceeds the limit");
4503 }
4504
4505 // the first two ICFs (before ADDBA_REQ and before first DL QoS Data) and the ICF before the
4506 // ADDBA_RESP are received by the main PHY. If aux PHYs do not switch links, the ICF before
4507 // the last DL QoS Data is also received by the main PHY
4508 NS_TEST_EXPECT_MSG_EQ((phyRecvIcf == mainPhy),
4509 (m_countIcfFrames == 1 || m_countIcfFrames == 2 ||
4511 "Expecting that the ICF was received by the main PHY");
4512
4513 // if aux PHYs do not switch links, the main PHY is operating on its original link when
4514 // the transmission of an ICF starts
4515 NS_TEST_EXPECT_MSG_EQ(m_switchAuxPhy || currMainPhyLinkId == m_mainPhyId,
4516 true,
4517 "Main PHY is operating on an unexpected link ("
4518 << +currMainPhyLinkId.value() << ", expected " << +m_mainPhyId
4519 << ")");
4520
4521 auto txDuration =
4522 WifiPhy::CalculateTxDuration(psduMap, txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand());
4523
4524 // check that PHYs are operating on the expected link after the reception of the ICF
4525 Simulator::Schedule(txDuration + NanoSeconds(1), [=, this]() {
4526 // the main PHY must be operating on the link where ICF was sent
4527 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId),
4528 mainPhy,
4529 "PHY operating on link where ICF was sent is not the main PHY");
4530
4531 // the behavior of Aux PHYs depends on whether they switch channel or not
4532 if (m_switchAuxPhy)
4533 {
4534 if (mainPhy != phyRecvIcf)
4535 {
4536 NS_TEST_EXPECT_MSG_EQ(phyRecvIcf->IsStateSwitching(),
4537 true,
4538 "Aux PHY expected to switch channel");
4539 }
4540 Simulator::Schedule(phyRecvIcf->GetChannelSwitchDelay(), [=, this]() {
4541 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(*currMainPhyLinkId),
4542 phyRecvIcf,
4543 "The Aux PHY that received the ICF is expected to operate "
4544 "on the link where Main PHY was before switching channel");
4545 });
4546 }
4547 else
4548 {
4549 NS_TEST_EXPECT_MSG_EQ(phyRecvIcf->IsStateSwitching(),
4550 false,
4551 "Aux PHY is not expected to switch channel");
4552 NS_TEST_EXPECT_MSG_EQ(phyRecvIcf->GetPhyBand(),
4553 mainPhy->GetPhyBand(),
4554 "The Aux PHY that received the ICF is expected to operate "
4555 "on the same band as the Main PHY");
4556 }
4557 });
4558}
4559
4560void
4562 const WifiTxVector& txVector,
4563 uint8_t linkId)
4564{
4565 // corrupt the first RTS frame (sent by the EMLSR client)
4566 if (++m_countRtsFrames == 1)
4567 {
4568 auto psdu = psduMap.begin()->second;
4569 m_errorModel->SetList({psdu->GetPacket()->GetUid()});
4570
4571 // check that when CTS timeout occurs, the main PHY is switching
4573 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetWifiTxTimer().GetDelayLeft() -
4574 TimeStep(1),
4575 [=, this]() {
4576 // store the time to complete the current channel switch at CTS timeout
4577 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
4578 auto toCurrSwitchEnd = mainPhy->GetDelayUntilIdle() + TimeStep(1);
4579
4580 Simulator::Schedule(TimeStep(1), [=, this]() {
4581 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
4582 true,
4583 "Main PHY expected to be in SWITCHING state instead of "
4584 << mainPhy->GetState()->GetState());
4585
4586 // If main PHY channel switch can be interrupted, the main PHY should be back
4587 // operating on the preferred link after a channel switch delay. Otherwise, it
4588 // will be operating on the preferred link, if SwitchAuxPhy is false, or on the
4589 // link used to send the RTS, if SwitchAuxPhy is true, after the remaining
4590 // channel switching time plus the channel switch delay.
4592 ? m_mainPhyId
4593 : linkId;
4594 auto delayLeft = m_resetCamStateAndInterruptSwitch
4595 ? Time{0}
4596 : toCurrSwitchEnd; // time to complete current switch
4598 {
4599 // add the time to perform another channel switch
4600 delayLeft += mainPhy->GetChannelSwitchDelay();
4601 }
4602
4603 auto totalSwitchDelay =
4604 delayLeft + (mainPhy->GetChannelSwitchDelay() - toCurrSwitchEnd);
4605
4606 Simulator::Schedule(delayLeft - TimeStep(1), [=, this]() {
4607 // check if the MSD timer was running on the link left by the main PHY
4608 // before completing channel switch
4609 bool msdWasRunning = m_staMacs[0]
4610 ->GetEmlsrManager()
4611 ->GetElapsedMediumSyncDelayTimer(m_mainPhyId)
4612 .has_value();
4613
4614 Simulator::Schedule(TimeStep(2), [=, this]() {
4615 auto id = m_staMacs[0]->GetLinkForPhy(mainPhy);
4616 NS_TEST_EXPECT_MSG_EQ(id.has_value(),
4617 true,
4618 "Expected main PHY to operate on a link");
4620 newLinkId,
4621 "Main PHY is operating on an unexpected link");
4622 const auto startMsd =
4623 (totalSwitchDelay >
4625 const auto msdIsRunning = msdWasRunning || startMsd;
4627 m_staMacs[0],
4629 msdIsRunning,
4630 std::string("because total switch delay was ") +
4631 std::to_string(totalSwitchDelay.GetNanoSeconds()) + "ns");
4632 });
4633 });
4634 });
4635 });
4636 }
4637 // block transmissions on all other links at non-AP MLD side
4638 std::set<uint8_t> links{0, 1, 2};
4639 links.erase(linkId);
4640 m_staMacs[0]->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
4641 AC_BE,
4643 m_apMac->GetAddress(),
4644 m_staMacs[0]->GetAddress(),
4645 {0},
4646 links);
4647}
4648
4649void
4651{
4652 NS_TEST_ASSERT_MSG_NE(m_txPsdusPos, 0, "BA agreement establishment not completed");
4653
4654 // Expected frame exchanges after ML setup and EML OMN exchange:
4655 // 1. (DL) ICF + CTS + ADDBA_REQ + ACK
4656 // 2. (UL) ADDBA_RESP + ACK
4657 // 3. (DL) ICF + CTS + DATA + BA
4658 // 4. (DL) ICF + CTS + DATA + BA
4659 // 5. (DL) ICF + CTS + DATA + BA
4660 // 6. (DL) ICF + CTS + DATA + BA
4661 // 7. (UL) ADDBA_REQ + ACK
4662 // 8. (DL) ICF + CTS + ADDBA_RESP + ACK
4663 // 9. (UL) DATA + BA
4664 // 10. (UL) RTS - CTS timeout
4665 // 11. (UL) (RTS + CTS + ) DATA + BA
4666
4667 // frame exchange 11 is protected if SwitchAuxPhy is false or (SwitchAuxPhy is true and) the
4668 // main PHY switch can be interrupted
4669 bool fe11protected = !m_switchAuxPhy || m_resetCamStateAndInterruptSwitch;
4670
4671 NS_TEST_EXPECT_MSG_EQ(m_countIcfFrames, 6, "Unexpected number of ICFs sent");
4672
4673 // frame exchanges without RTS because the EMLSR client sent the initial frame through main PHY
4674 const std::size_t nFrameExchNoRts = fe11protected ? 3 : 4;
4675
4676 const std::size_t nFrameExchWithRts = fe11protected ? 1 : 0;
4677
4679 m_txPsdusPos +
4680 m_countIcfFrames * 4 + /* frames in frame exchange with ICF */
4681 nFrameExchNoRts * 2 + /* frames in frame exchange without RTS */
4682 nFrameExchWithRts * 4 + /* frames in frame exchange with RTS */
4683 1, /* corrupted RTS */
4684 "Insufficient number of TX PSDUs");
4685
4686 // m_txPsdusPos points to the first ICF
4687 auto psduIt = std::next(m_txPsdus.cbegin(), m_txPsdusPos);
4688
4689 const std::size_t nFrameExchanges =
4690 m_countIcfFrames + nFrameExchNoRts + nFrameExchWithRts + 1 /* corrupted RTS */;
4691
4692 for (std::size_t i = 1; i <= nFrameExchanges; ++i)
4693 {
4694 if (i == 1 || (i >= 3 && i <= 6) || i == 8 || i == 10 || (i == 11 && fe11protected))
4695 {
4696 // frame exchanges with protection
4697 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
4698 (i < 9 ? psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsTrigger()
4699 : psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsRts())),
4700 true,
4701 "Expected a Trigger Frame (ICF)");
4702 psduIt++;
4703 if (i == 10)
4704 {
4705 continue; // corrupted RTS
4706 }
4707 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
4708 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsCts()),
4709 true,
4710 "Expected a CTS");
4711 psduIt++;
4712 }
4713
4714 if (i == 1 || i == 2 || i == 7 || i == 8) // frame exchanges with ADDBA REQ/RESP frames
4715 {
4716 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
4717 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsMgt()),
4718 true,
4719 "Expected a management frame");
4720 psduIt++;
4721 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
4722 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsAck()),
4723 true,
4724 "Expected a Normal Ack");
4725 }
4726 else
4727 {
4728 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
4729 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsQosData()),
4730 true,
4731 "Expected a QoS Data frame");
4732 psduIt++;
4733 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
4734 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsBlockAck()),
4735 true,
4736 "Expected a BlockAck");
4737 }
4738 psduIt++;
4739 }
4740}
4741
4743 : EmlsrOperationsTestBase(std::string("Check EMLSR link switching (auxPhyMaxChWidth=") +
4744 std::to_string(auxPhyMaxChWidth) + "MHz )"),
4745 m_auxPhyMaxChWidth(auxPhyMaxChWidth),
4746 m_channelSwitchDelay(MicroSeconds(75)),
4747 m_currMainPhyLinkId(0),
4748 m_nextMainPhyLinkId(0)
4749{
4750 m_nEmlsrStations = 1;
4752 m_linksToEnableEmlsrOn = {0, 1, 2}; // enable EMLSR on all links right after association
4753 m_mainPhyId = 1;
4754 m_establishBaUl = true;
4755 m_duration = Seconds(1);
4757}
4758
4759void
4767
4768void
4770{
4771 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(true));
4772 Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth", UintegerValue(m_auxPhyMaxChWidth));
4773 Config::SetDefault("ns3::EmlsrManager::AuxPhyMaxModClass", StringValue("EHT"));
4774 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(m_channelSwitchDelay));
4775
4777
4778 // use channels of different widths
4779 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
4780 {
4781 mac->GetWifiPhy(0)->SetOperatingChannel(
4783 mac->GetWifiPhy(1)->SetOperatingChannel(
4785 mac->GetWifiPhy(2)->SetOperatingChannel(
4787 }
4788}
4789
4790void
4792{
4793 m_staMacs[1]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 1, 1, 2000));
4794
4795 // force the transmission of the packet to happen now on the given link.
4796 // Multiple ScheduleNow calls are needed because Node::AddApplication() schedules a call to
4797 // Application::Initialize(), which schedules a call to Application::StartApplication(), which
4798 // schedules a call to PacketSocketClient::Send(), which finally generates the packet
4799 Simulator::ScheduleNow([=, this]() {
4800 Simulator::ScheduleNow([=, this]() {
4801 Simulator::ScheduleNow([=, this]() {
4802 m_staMacs[1]->GetFrameExchangeManager(linkId)->StartTransmission(
4803 m_staMacs[1]->GetQosTxop(AC_BE),
4804 m_staMacs[1]->GetWifiPhy(linkId)->GetChannelWidth());
4805 });
4806 });
4807 });
4808
4809 // check that the other MLD started transmitting on the correct link
4810 Simulator::Schedule(TimeStep(1), [=, this]() {
4811 NS_TEST_EXPECT_MSG_EQ(m_staMacs[1]->GetWifiPhy(linkId)->IsStateTx(),
4812 true,
4813 "At time " << Simulator::Now().As(Time::NS)
4814 << ", other MLD did not start transmitting on link "
4815 << +linkId);
4816 });
4817}
4818
4819void
4821{
4822 auto currMainPhyLinkId = m_staMacs[0]->GetLinkForPhy(m_mainPhyId);
4823 NS_TEST_ASSERT_MSG_EQ(currMainPhyLinkId.has_value(),
4824 true,
4825 "Main PHY is not operating on any link");
4826 m_currMainPhyLinkId = *currMainPhyLinkId;
4828
4829 // request the main PHY to switch to another link
4830 m_staMacs[0]->GetEmlsrManager()->SwitchMainPhy(
4832 false,
4835 EmlsrDlTxopIcfReceivedByAuxPhyTrace{}); // trace info not used
4836
4837 // the other MLD transmits a packet to the AP
4839
4840 // schedule another packet transmission slightly (10 us) before the end of aux PHY switch
4843 this,
4845
4846 // first checkpoint is after that the preamble of the PPDU has been received
4848}
4849
4850/**
4851 * ┌───────────────┐
4852 * [link X] │ other to AP │CP3
4853 * ──────────────────────────────┴───────────────┴──────────────────────────────────────────────
4854 * |------ main PHY ------| |------------------- aux PHY ---------------------
4855 * .\_ _/
4856 * . \_ _/
4857 * . \_ _/
4858 * . \_ _/
4859 * [link Y] . CP1 \/ CP2
4860 * .┌───────────────┐
4861 * .│ other to AP │
4862 * ─────────────────────────┴───────────────┴────────────────────────────────────────────────────
4863 * |------------ aux PHY ----------|---------------------- main PHY ----------------------------
4864 *
4865 */
4866
4867void
4869{
4870 // first checkpoint is after that the preamble of the first PPDU has been received
4871 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
4872
4873 // 1. Main PHY is switching
4874 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(), true, "Main PHY is not switching");
4875
4876 auto auxPhy = m_staMacs[0]->GetWifiPhy(m_nextMainPhyLinkId);
4877 NS_TEST_EXPECT_MSG_NE(mainPhy, auxPhy, "Main PHY is operating on an unexpected link");
4878
4879 // 2. Aux PHY is receiving the PHY header
4880 NS_TEST_EXPECT_MSG_EQ(auxPhy->GetInfoIfRxingPhyHeader().has_value(),
4881 true,
4882 "Aux PHY is not receiving a PHY header");
4883
4884 // 3. Main PHY dropped the preamble because it is switching
4885 NS_TEST_EXPECT_MSG_EQ(mainPhy->GetInfoIfRxingPhyHeader().has_value(),
4886 false,
4887 "Main PHY is receiving a PHY header");
4888
4889 // 4. Channel access manager on destination link (Y) has been notified of CCA busy, but not
4890 // until the end of transmission (main PHY dropped the preamble and notified CCA busy until
4891 // end of transmission but the channel access manager on link Y does not yet have a listener
4892 // attached to the main PHY; aux PHY notified CCA busy until the end of the PHY header field
4893 // being received)
4894 const auto caManager = m_staMacs[0]->GetChannelAccessManager(m_nextMainPhyLinkId);
4895 const auto endTxTime = m_staMacs[1]->GetChannelAccessManager(m_nextMainPhyLinkId)->m_lastTxEnd;
4896 NS_TEST_ASSERT_MSG_EQ(caManager->m_lastBusyEnd.contains(WIFI_CHANLIST_PRIMARY),
4897 true,
4898 "No CCA information for primary20 channel");
4900 caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
4902 "ChannelAccessManager on destination link not notified of CCA busy");
4904 caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
4905 endTxTime,
4906 "ChannelAccessManager on destination link notified of CCA busy until end of transmission");
4907
4908 // second checkpoint is after that the main PHY completed the link switch
4909 Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1),
4911 this);
4912}
4913
4914void
4916{
4917 // second checkpoint is after that the main PHY completed the link switch. The channel access
4918 // manager on destination link (Y) is expected to be notified by the main PHY that medium is
4919 // busy until the end of the ongoing transmission
4920 const auto caManager = m_staMacs[0]->GetChannelAccessManager(m_nextMainPhyLinkId);
4921 const auto endTxTime = m_staMacs[1]->GetChannelAccessManager(m_nextMainPhyLinkId)->m_lastTxEnd;
4922 NS_TEST_ASSERT_MSG_EQ(caManager->m_lastBusyEnd.contains(WIFI_CHANLIST_PRIMARY),
4923 true,
4924 "No CCA information for primary20 channel");
4926 caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
4928 "ChannelAccessManager on destination link not notified of CCA busy");
4929 NS_TEST_EXPECT_MSG_GT_OR_EQ(caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
4930 endTxTime,
4931 "ChannelAccessManager on destination link not notified of CCA busy "
4932 "until end of transmission");
4933
4934 // third checkpoint is after that the aux PHY completed the link switch
4936}
4937
4938void
4940{
4941 // third checkpoint is after that the aux PHY completed the link switch. The channel access
4942 // manager on source link (X) is expected to be notified by the aux PHY that medium is
4943 // busy until the end of the ongoing transmission (even if the aux PHY was not listening to
4944 // link X when transmission started, its interface on link X recorded the transmission)
4945 const auto caManager = m_staMacs[0]->GetChannelAccessManager(m_currMainPhyLinkId);
4946 const auto endTxTime = m_staMacs[1]->GetChannelAccessManager(m_currMainPhyLinkId)->m_lastTxEnd;
4947 NS_TEST_ASSERT_MSG_EQ(caManager->m_lastBusyEnd.contains(WIFI_CHANLIST_PRIMARY),
4948 true,
4949 "No CCA information for primary20 channel");
4950 NS_TEST_EXPECT_MSG_GT_OR_EQ(caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
4952 "ChannelAccessManager on source link not notified of CCA busy");
4953 NS_TEST_EXPECT_MSG_GT_OR_EQ(caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
4954 endTxTime,
4955 "ChannelAccessManager on source link not notified of CCA busy "
4956 "until end of transmission");
4957}
4958
4960 : TestSuite("wifi-emlsr", Type::UNIT)
4961{
4962 AddTestCase(new EmlOperatingModeNotificationTest(), TestCase::Duration::QUICK);
4963 AddTestCase(new EmlOmnExchangeTest({1, 2}, MicroSeconds(0)), TestCase::Duration::QUICK);
4964 AddTestCase(new EmlOmnExchangeTest({1, 2}, MicroSeconds(2048)), TestCase::Duration::QUICK);
4965 AddTestCase(new EmlOmnExchangeTest({0, 1, 2, 3}, MicroSeconds(0)), TestCase::Duration::QUICK);
4966 AddTestCase(new EmlOmnExchangeTest({0, 1, 2, 3}, MicroSeconds(2048)),
4967 TestCase::Duration::QUICK);
4968 for (const auto& emlsrLinks :
4969 {std::set<uint8_t>{0, 1, 2}, std::set<uint8_t>{1, 2}, std::set<uint8_t>{0, 1}})
4970 {
4972 0,
4973 emlsrLinks,
4974 {MicroSeconds(32)},
4975 {MicroSeconds(32)},
4976 MicroSeconds(512),
4977 true /* putAuxPhyToSleep */}),
4978 TestCase::Duration::QUICK);
4980 1,
4981 emlsrLinks,
4982 {MicroSeconds(64)},
4983 {MicroSeconds(64)},
4984 MicroSeconds(512),
4985 false /* putAuxPhyToSleep */}),
4986 TestCase::Duration::QUICK);
4988 2,
4989 emlsrLinks,
4990 {MicroSeconds(128), MicroSeconds(256)},
4991 {MicroSeconds(128), MicroSeconds(256)},
4992 MicroSeconds(512),
4993 true /* putAuxPhyToSleep */}),
4994 TestCase::Duration::QUICK);
4995 }
4996
4997 for (auto genBackoffAndUseAuxPhyCca : {true, false})
4998 {
4999 for (auto nSlotsLeft : std::initializer_list<uint8_t>{0, 4})
5000 {
5001 AddTestCase(new EmlsrUlTxopTest({{0, 1, 2},
5002 MHz_u{40},
5003 MHz_u{20},
5004 MicroSeconds(5504),
5005 3,
5006 genBackoffAndUseAuxPhyCca,
5007 nSlotsLeft,
5008 true, /* putAuxPhyToSleep */
5009 false /* switchMainPhyBackDelayTimeout */}),
5010 TestCase::Duration::QUICK);
5011 AddTestCase(new EmlsrUlTxopTest({{0, 1},
5012 MHz_u{40},
5013 MHz_u{20},
5014 MicroSeconds(5504),
5015 1,
5016 genBackoffAndUseAuxPhyCca,
5017 nSlotsLeft,
5018 false, /* putAuxPhyToSleep */
5019 true /* switchMainPhyBackDelayTimeout */}),
5020 TestCase::Duration::QUICK);
5021 }
5022 }
5023
5024 for (bool switchAuxPhy : {true, false})
5025 {
5026 for (bool resetCamStateAndInterruptSwitch : {true, false})
5027 {
5028 for (auto auxPhyMaxChWidth : {MHz_u{20}, MHz_u{40}, MHz_u{80}, MHz_u{160}})
5029 {
5031 {switchAuxPhy, resetCamStateAndInterruptSwitch, auxPhyMaxChWidth}),
5032 TestCase::Duration::QUICK);
5033 }
5034 }
5035 }
5036
5037 AddTestCase(new EmlsrUlOfdmaTest(false), TestCase::Duration::QUICK);
5038 AddTestCase(new EmlsrUlOfdmaTest(true), TestCase::Duration::QUICK);
5039
5040 AddTestCase(new EmlsrCcaBusyTest(MHz_u{20}), TestCase::Duration::QUICK);
5041 AddTestCase(new EmlsrCcaBusyTest(MHz_u{80}), TestCase::Duration::QUICK);
5042}
5043
5044static 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.
Test CCA busy notifications on EMLSR clients.
void DoSetup() override
Implementation to do any local setup required for this TestCase.
uint8_t m_nextMainPhyLinkId
the ID of the link the main PHY switches to
uint8_t m_currMainPhyLinkId
the ID of the link the main PHY switches from
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
MHz_u m_auxPhyMaxChWidth
max channel width supported by aux PHYs
void TransmitPacketToAp(uint8_t linkId)
Make the other MLD transmit a packet to the AP on the given link.
void DoRun() override
Implementation to actually run this TestCase.
Time m_channelSwitchDelay
the PHY channel switch delay
void CheckPoint1()
Perform checks after that the preamble of the first PPDU has been received.
void CheckPoint3()
Perform checks after that the aux PHY completed the link switch.
EmlsrCcaBusyTest(MHz_u auxPhyMaxChWidth)
Constructor.
void CheckPoint2()
Perform checks after that the main PHY completed the link switch.
Test the transmission of DL frames to EMLSR clients.
void CheckInitialControlFrame(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken by the AP MLD transmitting an initial Control frame to an EM...
const Time m_fe2to3delay
time interval between 2nd and 3rd frame exchange sequences after the enablement of EMLSR mode
void CheckResults()
Check that the simulation produced the expected results.
void CheckPmModeAfterAssociation(const Mac48Address &address)
Check that the AP MLD considers the correct Power Management mode for the links setup with the given ...
EmlsrDlTxopTest(const Params &params)
Constructor.
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
Ptr< ListErrorModel > m_errorModel
error rate model to corrupt BlockAck at AP MLD
void CheckStaEmlNotificationFrame(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken when an EMLSR client transmits an EML Operating Mode Notific...
std::size_t m_countQoSframes
counter for QoS frames (transition delay test)
void CheckApEmlNotificationFrame(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken when the AP MLD transmits an EML Operating Mode Notification...
Time m_emlsrEnabledTime
when EMLSR mode has been enabled on all EMLSR clients
std::set< uint8_t > m_emlsrLinks
IDs of the links on which EMLSR mode has to be enabled.
void DoSetup() override
Implementation to do any local setup required for this TestCase.
void CheckQosFrames(const WifiConstPsduMap &psduMap, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken by the AP MLD transmitting a PPDU containing QoS data frames...
void Transmit(Ptr< WifiMac > mac, uint8_t phyId, WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW) override
Callback invoked when a FEM passes PSDUs to the PHY.
void EnableEmlsrMode()
Enable EMLSR mode on the next EMLSR client.
void CheckBlockAck(const WifiConstPsduMap &psduMap, const WifiTxVector &txVector, uint8_t phyId)
Check that appropriate actions are taken by the AP MLD receiving a PPDU containing BlockAck frames fr...
void DoRun() override
Implementation to actually run this TestCase.
std::size_t m_countBlockAck
counter for BlockAck frames (transition delay test)
Base class for EMLSR Operations tests.
void MainPhySwitchInfoCallback(std::size_t index, const EmlsrMainPhySwitchTrace &info)
Callback connected to the EMLSR Manager MainPhySwitch trace source.
std::size_t m_nNonEmlsrStations
number of stations to create that do not activate EMLSR
void SetSsid(uint16_t aid, Mac48Address)
Set the SSID on the next station that needs to start the association procedure.
bool m_establishBaDl
whether BA needs to be established (for TID 0) with the AP as originator
void CheckBlockedLink(Ptr< WifiMac > mac, Mac48Address dest, uint8_t linkId, WifiQueueBlockedReason reason, bool blocked, std::string description, bool testUnblockedForOtherReasons=true)
Check whether QoS data unicast transmissions addressed to the given destination on the given link are...
std::size_t m_nEmlsrStations
number of stations to create that activate EMLSR
std::vector< PacketSocketAddress > m_dlSockets
packet socket address for DL traffic
std::vector< Time > m_paddingDelay
Padding Delay advertised by the non-AP MLD.
std::set< uint8_t > m_linksToEnableEmlsrOn
IDs of the links on which EMLSR mode has to be enabled.
Ptr< ApWifiMac > m_apMac
AP wifi MAC.
bool m_putAuxPhyToSleep
whether aux PHYs are put to sleep during DL/UL TXOPs
TrafficDirection
Enumeration for traffic directions.
void CheckMsdTimerRunning(Ptr< StaWifiMac > staMac, uint8_t linkId, bool isRunning, const std::string &msg)
Check whether the MediumSyncDelay timer is running on the given link of the given device.
void DoSetup() override
Implementation to do any local setup required for this TestCase.
uint8_t m_mainPhyId
ID of the main PHY.
Time m_duration
simulation duration
std::vector< FrameInfo > m_txPsdus
transmitted PSDUs
Ptr< PacketSocketClient > GetApplication(TrafficDirection dir, std::size_t staId, std::size_t count, std::size_t pktSize) const
virtual void Transmit(Ptr< WifiMac > mac, uint8_t phyId, WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW)
Callback invoked when a FEM passes PSDUs to the PHY.
bool m_establishBaUl
whether BA needs to be established (for TID 0) with the AP as recipient
uint16_t m_lastAid
AID of last associated station.
std::vector< Time > m_transitionDelay
Transition Delay advertised by the non-AP MLD.
void CheckMainPhyTraceInfo(std::size_t index, std::string_view reason, const std::optional< uint8_t > &fromLinkId, uint8_t toLinkId, bool checkFromLinkId=true, bool checkToLinkId=true)
Check information provided by the EMLSR Manager MainPhySwitch trace.
std::map< std::size_t, std::shared_ptr< EmlsrMainPhySwitchTrace > > m_traceInfo
EMLSR client ID-indexed map of trace info from last main PHY switch.
Time m_transitionTimeout
Transition Timeout advertised by the AP MLD.
std::vector< PacketSocketAddress > m_ulSockets
packet socket address for UL traffic
void CheckAuxPhysSleepMode(Ptr< StaWifiMac > staMac, bool sleep)
Check whether aux PHYs of the given device are in sleep mode/awake.
std::vector< Ptr< StaWifiMac > > m_staMacs
MACs of the non-AP MLDs.
virtual void StartTraffic()
Start the generation of traffic (needs to be overridden)
EmlsrOperationsTestBase(const std::string &name)
Constructor.
Check UL OFDMA operations with EMLSR clients.
Time m_startAccessReq
start time of the first AP MLD access request via MU scheduler
void DoRun() override
Implementation to actually run this TestCase.
void CheckResults()
Check that the simulation produced the expected results.
EmlsrUlOfdmaTest(bool enableBsrp)
Constructor.
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
void Transmit(Ptr< WifiMac > mac, uint8_t phyId, WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW) override
Callback invoked when a FEM passes PSDUs to the PHY.
void DoSetup() override
Implementation to do any local setup required for this TestCase.
bool m_enableBsrp
whether MU scheduler sends BSRP TFs
std::size_t m_txPsdusPos
position in the vector of TX PSDUs of the first ICF
Test the transmission of UL frames from EMLSR clients.
std::size_t m_countQoSframes
counter for QoS frames
const Time m_unblockMainPhyLinkDelay
delay between the time the first two UL packets are generated and the time transmissions are unblocke...
Ptr< ListErrorModel > m_errorModel
error rate model to corrupt packets
bool m_useAuxPhyCca
whether CCA info from aux PHY is used when aux PHY is not TX capable
Time m_5thQosFrameTxTime
start transmission time of the 5th QoS data frame
MHz_u m_5thQosFrameExpWidth
expected width of the 5th QoS data frame
MHz_u m_auxPhyChannelWidth
max width supported by aux PHYs
void BackoffGenerated(uint32_t backoff, uint8_t linkId)
Callback invoked when a new backoff value is generated by the EMLSR client.
bool m_switchMainPhyBackDelayTimeout
whether a SwitchMainPhyBackDelay timer expires after that the main PHY moved to an aux PHY link
std::optional< uint8_t > m_nonEmlsrLink
ID of the non-EMLSR link (if any)
Time m_lastMsdExpiryTime
expiry time of the last MediumSyncDelay timer
void DoSetup() override
Implementation to do any local setup required for this TestCase.
std::size_t m_countRtsframes
counter for RTS frames
void CheckCtsFrames(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken by the EMLSR client when receiving a CTS frame on the given ...
void DoRun() override
Implementation to actually run this TestCase.
Time m_firstUlPktsGenTime
generation time of the first two UL packets
std::optional< bool > m_corruptCts
whether the transmitted CTS must be corrupted
void CheckBlockAck(const WifiConstPsduMap &psduMap, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken when an MLD transmits a PPDU containing BlockAck frames on t...
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
std::optional< Time > m_backoffEndTime
expected backoff end time on main PHY link
MHz_u m_channelWidth
width of the channels used by MLDs
std::set< uint8_t > m_emlsrLinks
IDs of the links on which EMLSR mode has to be enabled.
Time m_mediumSyncDuration
duration of the MediumSyncDelay timer
void CheckRtsFrames(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken by the EMLSR client when transmitting an RTS frame on the gi...
void CheckQosFrames(const WifiConstPsduMap &psduMap, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken when an MLD transmits a PPDU containing QoS data frames on t...
bool m_genBackoffIfTxopWithoutTx
whether the backoff should be invoked when the AC gains the right to start a TXOP but it does not tra...
void Transmit(Ptr< WifiMac > mac, uint8_t phyId, WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW) override
Callback invoked when a FEM passes PSDUs to the PHY.
uint8_t m_nSlotsLeftAlert
value for ChannelAccessManager NSlotsLeft attribute
bool m_checkBackoffStarted
whether we are checking the generated backoff values
std::size_t m_countBlockAck
counter for BlockAck frames
void CheckResults()
Check that the simulation produced the expected results.
uint8_t m_msdMaxNTxops
Max number of TXOPs that an EMLSR client is allowed to attempt to initiate while the MediumSyncDelay ...
EmlsrUlTxopTest(const Params &params)
Constructor.
wifi EMLSR Test Suite
A container for one type of attribute.
Headers for BlockAck response.
Headers for Trigger frames.
bool IsMuRts() const
Check if this is a MU-RTS Trigger frame.
void SetPaddingSize(std::size_t size)
Set the size in bytes of the Padding field.
TriggerFrameType GetType() const
Get the Trigger Frame type.
std::size_t GetNUserInfoFields() const
Get the number of User Info fields in this Trigger Frame.
Class for representing data rates.
Definition data-rate.h:78
Time CalculateBytesTxTime(uint32_t bytes) const
Calculate transmission time.
Definition data-rate.cc:220
static constexpr uint16_t MEDIUM_SYNC_THRESHOLD_USEC
The aMediumSyncThreshold defined by Sec. 35.3.16.18.1 of 802.11be D4.0.
static constexpr bool DONT_REQUEST_ACCESS
do not request channel access when PHY switch ends
static constexpr bool DONT_RESET_BACKOFF
do not reset backoff on main PHY switch
Hold variables of type enum.
Definition enum.h:52
Subclass of TestCase class adding the ability to test the serialization and deserialization of a Head...
void TestHeaderSerialization(const T &hdr, Args &&... args)
Serialize the given header in a buffer, then create a new header by deserializing from the buffer and...
void SetList(const std::list< uint64_t > &packetlist)
an EUI-48 address
static Mac48Address GetBroadcast()
Implement the header for management frames of type association request.
Implement the header for management frames of type association and reassociation response.
Implement the header for Action frames of type EML Operating Mode Notification.
void SetLinkIdInBitmap(uint8_t linkId)
Set the bit position in the link bitmap corresponding to the given link.
EmlControl m_emlControl
EML Control field.
std::optional< EmlsrParamUpdate > m_emlsrParamUpdate
EMLSR Parameter Update field.
std::list< uint8_t > GetLinkBitmap() const
Helper class used to assign positions and mobility models to nodes.
MultiUserScheduler is an abstract base class defining the API that APs supporting at least VHT can us...
holds a vector of ns3::NetDevice pointers
Ptr< NetDevice > Get(uint32_t i) const
Get the Ptr<NetDevice> stored in this container at a given index.
keep track of a set of node pointers.
static Iterator Begin()
Definition node-list.cc:226
static Iterator End()
Definition node-list.cc:233
an address for a packet socket
void SetProtocol(uint16_t protocol)
Set the protocol.
void SetSingleDevice(uint32_t device)
Set the address to match only a specified NetDevice.
Give ns3::PacketSocket powers to ns3::Node.
void Install(Ptr< Node > node) const
Aggregate an instance of a ns3::PacketSocketFactory onto the provided node.
Smart pointer class similar to boost::intrusive_ptr.
static void SetRun(uint64_t run)
Set the run number of simulation.
static void SetSeed(uint32_t seed)
Set the seed.
static EventId Schedule(const Time &delay, FUNC f, Ts &&... args)
Schedule an event to expire after delay.
Definition simulator.h:560
static void Destroy()
Execute the events scheduled with ScheduleDestroy().
Definition simulator.cc:131
static Time Now()
Return the current simulation virtual time.
Definition simulator.cc:197
static void Run()
Run the simulation.
Definition simulator.cc:167
static EventId ScheduleNow(FUNC f, Ts &&... args)
Schedule an event to expire Now.
Definition simulator.h:594
static void Stop()
Tell the Simulator the calling event should be the last one executed.
Definition simulator.cc:175
Make it easy to create and manage PHY objects for the spectrum model.
void AddChannel(const Ptr< SpectrumChannel > channel, const FrequencyRange &freqRange=WHOLE_WIFI_SPECTRUM)
The IEEE 802.11 SSID Information Element.
Definition ssid.h:25
Hold variables of type string.
Definition string.h:45
encapsulates test code
Definition test.h:1050
void AddTestCase(TestCase *testCase, Duration duration=Duration::QUICK)
Add an individual child TestCase to this test suite.
Definition test.cc:292
A suite of tests to run.
Definition test.h:1267
Type
Type of test.
Definition test.h:1274
Simulation virtual time values and global simulation resolution.
Definition nstime.h:94
TimeWithUnit As(const Unit unit=Time::AUTO) const
Attach a unit to a Time, to facilitate output in a specific unit.
Definition time.cc:404
bool IsStrictlyPositive() const
Exactly equivalent to t > 0.
Definition nstime.h:340
@ US
microsecond
Definition nstime.h:107
@ MS
millisecond
Definition nstime.h:106
@ NS
nanosecond
Definition nstime.h:108
static Time Max()
Maximum representable Time Not to be confused with Max(Time,Time).
Definition nstime.h:286
bool IsZero() const
Exactly equivalent to t == 0.
Definition nstime.h:304
int64_t GetTimeStep() const
Get the raw time value, in the current resolution unit.
Definition nstime.h:434
Time Get() const
Definition time.cc:519
Hold an unsigned integer type.
Definition uinteger.h:34
See IEEE 802.11 chapter 7.3.1.11 Header format: | category: 1 | action value: 1 |.
static std::pair< CategoryValue, ActionValue > Peek(Ptr< const Packet > pkt)
Peek an Action header from the given packet.
void Print(std::ostream &os) const override
static std::pair< CategoryValue, ActionValue > Remove(Ptr< Packet > pkt)
Remove an Action header from the given packet.
helps to create WifiNetDevice objects
static int64_t AssignStreams(NetDeviceContainer c, int64_t stream)
Assign a fixed random variable stream number to the random variables used by the PHY and MAC aspects ...
Implements the IEEE 802.11 MAC header.
create MAC layers for a ns3::WifiNetDevice.
uint64_t GetDataRate(MHz_u channelWidth, Time guardInterval, uint8_t nss) const
Definition wifi-mode.cc:111
void SetPcapCaptureType(PcapCaptureType type)
Set the PCAP capture type to be used.
void SetPcapDataLinkType(SupportedPcapDataLinkTypes dlt)
Set the data link type of PCAP traces to be used.
void Set(std::string name, const AttributeValue &v)
@ DLT_IEEE802_11_RADIO
Include Radiotap link layer information.
std::tuple< uint8_t, MHz_u, WifiPhyBand, uint8_t > ChannelTuple
Tuple identifying a segment of an operating channel.
Definition wifi-phy.h:940
static Time CalculateTxDuration(uint32_t size, const WifiTxVector &txVector, WifiPhyBand band, uint16_t staId=SU_STA_ID)
Definition wifi-phy.cc:1587
static Time CalculatePhyPreambleAndHeaderDuration(const WifiTxVector &txVector)
Definition wifi-phy.cc:1580
This class mimics the TXVECTOR which is to be passed to the PHY in order to define the parameters whi...
WifiMode GetMode(uint16_t staId=SU_STA_ID) const
If this TX vector is associated with an SU PPDU, return the selected payload transmission mode.
WifiPreamble GetPreambleType() const
MHz_u GetChannelWidth() const
#define NS_ASSERT_MSG(condition, message)
At runtime, in debugging builds, if this condition is not true, the program prints the message to out...
Definition assert.h:75
void SetDefault(std::string name, const AttributeValue &value)
Definition config.cc:883
void ConnectWithoutContext(std::string path, const CallbackBase &cb)
Definition config.cc:943
#define NS_ABORT_MSG_IF(cond, msg)
Abnormal program termination if a condition is true, with a message.
Definition abort.h:97
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition log.h:191
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
Definition log.h:257
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition log.h:264
Ptr< T > CreateObject(Args &&... args)
Create an object by type, with varying number of constructor parameters.
Definition object.h:619
Ptr< T > CreateObjectWithAttributes(Args... args)
Allocate an Object on the heap and initialize with a set of attributes.
Ptr< T > Create(Ts &&... args)
Create class instances by constructors with varying numbers of arguments and return them by Ptr.
Definition ptr.h:436
#define NS_TEST_EXPECT_MSG_GT_OR_EQ(actual, limit, msg)
Test that an actual value is greater than or equal to limit and report if not.
Definition test.h:986
#define NS_TEST_ASSERT_MSG_EQ(actual, limit, msg)
Test that an actual and expected (limit) value are equal and report and abort if not.
Definition test.h:134
#define NS_TEST_EXPECT_MSG_LT_OR_EQ(actual, limit, msg)
Test that an actual value is less than or equal to a limit and report if not.
Definition test.h:820
#define NS_TEST_EXPECT_MSG_LT(actual, limit, msg)
Test that an actual value is less than a limit and report if not.
Definition test.h:780
#define NS_TEST_EXPECT_MSG_GT(actual, limit, msg)
Test that an actual value is greater than a limit and report if not.
Definition test.h:946
#define NS_TEST_EXPECT_MSG_NE(actual, limit, msg)
Test that an actual and expected (limit) value are not equal and report if not.
Definition test.h:656
#define NS_TEST_EXPECT_MSG_EQ(actual, limit, msg)
Test that an actual and expected (limit) value are equal and report if not.
Definition test.h:241
#define NS_TEST_ASSERT_MSG_NE(actual, limit, msg)
Test that an actual and expected (limit) value are not equal and report and abort if not.
Definition test.h:554
#define NS_TEST_ASSERT_MSG_GT(actual, limit, msg)
Test that an actual value is greater than a limit and report and abort if not.
Definition test.h:864
#define NS_TEST_ASSERT_MSG_GT_OR_EQ(actual, limit, msg)
Test that an actual value is greater than or equal to a limit and report and abort if not.
Definition test.h:905
Time MicroSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1368
Time NanoSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1380
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition nstime.h:1344
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1356
WifiMacDropReason
The reason why an MPDU was dropped.
Definition wifi-mac.h:71
WifiQueueBlockedReason
Enumeration of the reasons to block container queues.
TriggerFrameType
The different Trigger frame types.
@ WIFI_STANDARD_80211be
@ WIFI_PREAMBLE_HT_MF
@ WIFI_PHY_BAND_6GHZ
The 6 GHz band.
@ WIFI_PHY_BAND_2_4GHZ
The 2.4 GHz band.
@ WIFI_PHY_BAND_5GHZ
The 5 GHz band.
@ WIFI_CHANLIST_PRIMARY
@ AC_BE
Best Effort.
Definition qos-utils.h:64
Every class exported by the ns3 library is enclosed in the ns3 namespace.
constexpr FrequencyRange WIFI_SPECTRUM_6_GHZ
Identifier for the frequency range covering the wifi spectrum in the 6 GHz band.
U * PeekPointer(const Ptr< U > &p)
Definition ptr.h:443
std:: tuple< WifiContainerQueueType, WifiReceiverAddressType, Mac48Address, std::optional< uint8_t > > WifiContainerQueueId
Tuple (queue type, receiver address type, Address, TID) identifying a container queue.
Callback< R, Args... > MakeCallback(R(T::*memPtr)(Args...), OBJ objPtr)
Build Callbacks for class method members which take varying numbers of arguments and potentially retu...
Definition callback.h:684
bool IsTrigger(const WifiPsduMap &psduMap)
Ptr< T1 > DynamicCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:580
std::size_t Count20MHzSubchannels(MHz_u channelWidth)
Return the number of 20 MHz subchannels covering the channel width.
Definition wifi-utils.h:135
constexpr FrequencyRange WIFI_SPECTRUM_5_GHZ
Identifier for the frequency range covering the wifi spectrum in the 5 GHz band.
@ WIFI_MAC_CTL_TRIGGER
@ WIFI_MAC_CTL_RTS
@ WIFI_MAC_CTL_CTS
@ WIFI_MAC_MGT_ACTION
@ WIFI_MAC_MGT_ASSOCIATION_RESPONSE
@ WIFI_MAC_MGT_ASSOCIATION_REQUEST
@ WIFI_MAC_CTL_BACKRESP
@ WIFI_MAC_CTL_END
@ WIFI_MAC_QOSDATA
Ptr< T1 > StaticCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:587
std::unordered_map< uint16_t, Ptr< const WifiPsdu > > WifiConstPsduMap
Map of const PSDUs indexed by STA-ID.
Definition wifi-ppdu.h:38
uint32_t GetAckSize()
Return the total Ack size (including FCS trailer).
Definition wifi-utils.cc:52
static constexpr uint16_t SU_STA_ID
STA_ID to identify a single user (SU)
Definition wifi-mode.h:24
static constexpr uint8_t MAX_PROPAGATION_DELAY_USEC
maximum propagation delay
constexpr FrequencyRange WIFI_SPECTRUM_2_4_GHZ
Identifier for the frequency range covering the wifi spectrum in the 2.4 GHz band.
STL namespace.
phy
Definition third.py:78
ns3::Time timeout
Parameters for the EMLSR DL TXOP test.
Parameters for the EMLSR UL TXOP test.
static uint8_t EncodeEmlsrTransitionDelay(Time delay)
static Time DecodeEmlsrTransitionDelay(uint8_t value)
static Time DecodeEmlsrPaddingDelay(uint8_t value)
static uint8_t EncodeEmlsrPaddingDelay(Time delay)
Struct to trace that main PHY switched to start a DL TXOP after that an aux PHY received an ICF.
Base struct for EMLSR Main PHY switch traces.
virtual std::shared_ptr< EmlsrMainPhySwitchTrace > Clone() const =0
Struct to trace that main PHY switched when a (DL or UL) TXOP ended.
uint8_t emlsrParamUpdateCtrl
EMLSR Parameter Update Control.
std::optional< uint16_t > linkBitmap
EMLSR/EMLMR Link Bitmap.
EMLSR Parameter Update field.
std::string dir
uint32_t pktSize
packet size used for the simulation (in bytes)
static WifiEmlsrTestSuite g_wifiEmlsrTestSuite
the test suite