A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
wifi-emlsr-link-switch-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
10
11#include "ns3/advanced-emlsr-manager.h"
12#include "ns3/boolean.h"
13#include "ns3/config.h"
14#include "ns3/eht-configuration.h"
15#include "ns3/eht-frame-exchange-manager.h"
16#include "ns3/interference-helper.h"
17#include "ns3/log.h"
18#include "ns3/mgt-action-headers.h"
19#include "ns3/multi-model-spectrum-channel.h"
20#include "ns3/pointer.h"
21#include "ns3/qos-txop.h"
22#include "ns3/simulator.h"
23#include "ns3/spectrum-wifi-phy.h"
24#include "ns3/string.h"
25#include "ns3/wifi-net-device.h"
26#include "ns3/wifi-spectrum-phy-interface.h"
27
28#include <algorithm>
29#include <functional>
30#include <iomanip>
31
32using namespace ns3;
33
34NS_LOG_COMPONENT_DEFINE("WifiEmlsrLinkSwitchTest");
35
38 std::string("Check EMLSR link switching (switchAuxPhy=") +
39 std::to_string(params.switchAuxPhy) + ", resetCamStateAndInterruptSwitch=" +
40 std::to_string(params.resetCamStateAndInterruptSwitch) +
41 ", auxPhyMaxChWidth=" + std::to_string(params.auxPhyMaxChWidth) + "MHz )"),
42 m_switchAuxPhy(params.switchAuxPhy),
43 m_resetCamStateAndInterruptSwitch(params.resetCamStateAndInterruptSwitch),
44 m_auxPhyMaxChWidth(params.auxPhyMaxChWidth),
45 m_countQoSframes(0),
46 m_countIcfFrames(0),
47 m_countRtsFrames(0),
48 m_txPsdusPos(0)
49{
52 m_linksToEnableEmlsrOn = {0, 1, 2}; // enable EMLSR on all links right after association
53 m_mainPhyId = 1;
54 m_establishBaDl = {0};
55 m_duration = Seconds(1.0);
56 // when aux PHYs do not switch link, the main PHY switches back to its previous link after
57 // a TXOP, hence the transition delay must exceed the channel switch delay (default: 250us)
59}
60
61void
63 uint8_t phyId,
64 WifiConstPsduMap psduMap,
65 WifiTxVector txVector,
66 double txPowerW)
67{
68 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
69 auto linkId = m_txPsdus.back().linkId;
70
71 auto psdu = psduMap.begin()->second;
72 auto nodeId = mac->GetDevice()->GetNode()->GetId();
73
74 switch (psdu->GetHeader(0).GetType())
75 {
77 NS_ASSERT_MSG(nodeId > 0, "APs do not send AssocReq frames");
78 NS_TEST_EXPECT_MSG_EQ(+linkId, +m_mainPhyId, "AssocReq not sent by the main PHY");
79 break;
80
82 auto [category, action] = WifiActionHeader::Peek(psdu->GetPayload(0));
83
84 if (nodeId == 1 && category == WifiActionHeader::PROTECTED_EHT &&
85 action.protectedEhtAction ==
87 {
88 // the EMLSR client is starting the transmission of the EML OMN frame;
89 // temporarily block transmissions of QoS data frames from the AP MLD to the
90 // non-AP MLD on all the links but the one used for ML setup, so that we know
91 // that the first QoS data frame is sent on the link of the main PHY
92 std::set<uint8_t> linksToBlock;
93 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
94 {
95 if (id != m_mainPhyId)
96 {
97 linksToBlock.insert(id);
98 }
99 }
100 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
101 AC_BE,
103 m_staMacs[0]->GetAddress(),
104 m_apMac->GetAddress(),
105 {0},
106 linksToBlock);
107 }
108 }
109 break;
110
112 CheckInitialControlFrame(psduMap, txVector, linkId);
113 break;
114
115 case WIFI_MAC_QOSDATA:
116 CheckQosFrames(psduMap, txVector, linkId);
117 break;
118
119 case WIFI_MAC_CTL_RTS:
120 CheckRtsFrame(psduMap, txVector, linkId);
121 break;
122
123 default:;
124 }
125}
126
127void
129{
130 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(m_switchAuxPhy));
131 Config::SetDefault("ns3::EmlsrManager::ResetCamState",
133 Config::SetDefault("ns3::AdvancedEmlsrManager::InterruptSwitch",
135 Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth", UintegerValue(m_auxPhyMaxChWidth));
136 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(45)));
137
139
141 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); ++linkId)
142 {
143 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
144 }
145
146 // use channels of different widths
147 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0]})
148 {
149 mac->GetWifiPhy(0)->SetOperatingChannel(
151 mac->GetWifiPhy(1)->SetOperatingChannel(
153 mac->GetWifiPhy(2)->SetOperatingChannel(
155 }
156}
157
158void
168
169void
171 const WifiTxVector& txVector,
172 uint8_t linkId)
173{
175
176 switch (m_countQoSframes)
177 {
178 case 1:
179 // unblock transmissions on all links
180 m_apMac->GetMacQueueScheduler()->UnblockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
181 AC_BE,
183 m_staMacs[0]->GetAddress(),
184 m_apMac->GetAddress(),
185 {0},
186 {0, 1, 2});
187 // block transmissions on the link used for ML setup
188 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
189 AC_BE,
191 m_staMacs[0]->GetAddress(),
192 m_apMac->GetAddress(),
193 {0},
194 {m_mainPhyId});
195 // generate a new data packet, which will be sent on a link other than the one
196 // used for ML setup, hence triggering a link switching on the EMLSR client
197 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 0, 2, 1000));
198 break;
199 case 2:
200 // block transmission on the link used to send this QoS data frame
201 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
202 AC_BE,
204 m_staMacs[0]->GetAddress(),
205 m_apMac->GetAddress(),
206 {0},
207 {linkId});
208 // generate a new data packet, which will be sent on the link that has not been used
209 // so far, hence triggering another link switching on the EMLSR client
210 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 0, 2, 1000));
211 break;
212 case 3:
213 // block transmission on the link used to send this QoS data frame
214 m_apMac->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
215 AC_BE,
217 m_staMacs[0]->GetAddress(),
218 m_apMac->GetAddress(),
219 {0},
220 {linkId});
221 // unblock transmissions on the link used for ML setup
222 m_apMac->GetMacQueueScheduler()->UnblockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
223 AC_BE,
225 m_staMacs[0]->GetAddress(),
226 m_apMac->GetAddress(),
227 {0},
228 {m_mainPhyId});
229 // generate a new data packet, which will be sent again on the link used for ML setup,
230 // hence triggering yet another link switching on the EMLSR client
231 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 0, 2, 1000));
232 break;
233 case 4:
234 // block transmissions on all links at non-AP MLD side
235 m_staMacs[0]->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
236 AC_BE,
238 m_apMac->GetAddress(),
239 m_staMacs[0]->GetAddress(),
240 {0},
241 {0, 1, 2});
242 // unblock transmissions on the link used for ML setup (non-AP MLD side)
243 m_staMacs[0]->GetMacQueueScheduler()->UnblockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
244 AC_BE,
246 m_apMac->GetAddress(),
247 m_staMacs[0]->GetAddress(),
248 {0},
249 {m_mainPhyId});
250 // trigger establishment of BA agreement with AP as recipient
251 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 4, 1000));
252 break;
253 case 5:
254 // unblock transmissions on all links at non-AP MLD side
255 m_staMacs[0]->GetMacQueueScheduler()->UnblockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
256 AC_BE,
258 m_apMac->GetAddress(),
259 m_staMacs[0]->GetAddress(),
260 {0},
261 {0, 1, 2});
262 // block transmissions on the link used for ML setup (non-AP MLD side)
263 m_staMacs[0]->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
264 AC_BE,
266 m_apMac->GetAddress(),
267 m_staMacs[0]->GetAddress(),
268 {0},
269 {m_mainPhyId});
270 // generate a new data packet, which will be sent on a link other than the one
271 // used for ML setup, hence triggering a link switching on the EMLSR client
272 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
273 break;
274 }
275}
276
277/**
278 * AUX PHY switching enabled (X = channel switch delay)
279 *
280 * |--------- aux PHY A ---------|------ main PHY ------|-------------- aux PHY B -------------
281 * ┌───┐ ┌───┐
282 * │ICF│ │QoS│
283 * ──────────────────────────┴───┴┬───┬┴───┴┬──┬────────────────────────────────────────────────
284 * [link 0] │CTS│ │BA│
285 * └───┘ └──┘
286 *
287 *
288 * |--------- main PHY ----------|------------------ aux PHY A ----------------|--- main PHY ---
289 * ┌───┐ ┌───┐ ┌───┐ ┌───┐
290 * │ICF│ │QoS│ │ICF│ │QoS│
291 * ───┴───┴┬───┬┴───┴┬──┬──────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬──
292 * [link 1]│CTS│ │BA│ │CTS│ │BA│
293 * └───┘ └──┘ └───┘ └──┘
294 *
295 *
296 * |--------------------- aux PHY B --------------------|------ main PHY ------|-- aux PHY A ---
297 * ┌───┐ ┌───┐
298 * │ICF│ │QoS│
299 * ─────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬─────────────────────────
300 * [link 2] │CTS│ │BA│
301 * └───┘ └──┘
302 *
303 * ... continued ...
304 *
305 * |----------------------------------------- aux PHY B ---------------------------------------
306 * ─────────────────────────────────────────────────────────────────────────────────────────────
307 * [link 0]
308 *
309 * |--------- main PHY ----------|X|X|------------------------ aux PHY A ----------------------
310 * ┌───┐
311 * │ACK│
312 * ──────────┬───┬┴───┴────────────────────────────────────────────────────────────────────────
313 * [link 1] │QoS│
314 * └───┘
315 *
316 * |-------- aux PHY A ----------|X|---------------------- main PHY ---------------------------
317 * ┌──┐
318 * │BA│
319 * ────────────────────────┬───X──────┬───┬┴──┴────────────────────────────────────────────────
320 * [link 2] │RTS│ │QoS│
321 * └───┘ └───┘
322 ************************************************************************************************
323 *
324 * AUX PHY switching disabled (X = channel switch delay)
325 *
326 * |------------------------------------------ aux PHY A ---------------------------------------
327 * |-- main PHY --|X|
328 * ┌───┐ ┌───┐
329 * │ICF│ │QoS│
330 * ──────────────────────────┴───┴┬───┬┴───┴┬──┬────────────────────────────────────────────────
331 * [link 0] │CTS│ │BA│
332 * └───┘ └──┘
333 *
334 * |-main|
335 * |--------- main PHY ----------| |-PHY-| |------ main PHY ------
336 * ┌───┐ ┌───┐ ┌───┐ ┌───┐
337 * │ICF│ │QoS│ │ICF│ │QoS│
338 * ───┴───┴┬───┬┴───┴┬──┬──────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬──
339 * [link 1]│CTS│ │BA│ │CTS│ │BA│
340 * └───┘ └──┘ └───┘ └──┘
341 *
342 *
343 * |------------------------------------------ aux PHY B ---------------------------------------
344 * |-- main PHY --|X|
345 * ┌───┐ ┌───┐
346 * │ICF│ │QoS│
347 * ─────────────────────────────────────────────────┴───┴┬───┬┴───┴┬──┬─────────────────────────
348 * [link 2] │CTS│ │BA│
349 * └───┘ └──┘
350 *
351 * ... continued ...
352 *
353 * |----------------------------------------- aux PHY A ---------------------------------------
354 * ─────────────────────────────────────────────────────────────────────────────────────────────
355 * [link 0]
356 *
357 * |-------- main PHY --------| |--- main PHY ---|
358 * ┌───┐
359 * │ACK│
360 * ──────────┬───┬┴───┴────────────────────────────────────────────────────────────────────────
361 * [link 1] │QoS│
362 * └───┘
363 *
364 * |------------------------------------------ aux PHY B --------------------------------------
365 * |X||X| |X|-------------- main PHY --------------
366 * ┌───┐ ┌──┐
367 * │CTS│ │BA│
368 * ────────────────────────┬───X───────────────┬───┬┴───┴┬───┬┴──┴─────────────────────────────
369 * [link 2] │RTS│ │RTS│ │QoS│
370 * └───┘ └───┘ └───┘
371 *
372 */
373
374void
376 const WifiTxVector& txVector,
377 uint8_t linkId)
378{
379 if (++m_countIcfFrames == 1)
380 {
381 m_txPsdusPos = m_txPsdus.size() - 1;
382 }
383
384 // the first ICF is sent to protect ADDBA_REQ for DL BA agreement, then one ICF is sent before
385 // each of the 4 DL QoS Data frames; finally, another ICF is sent before the ADDBA_RESP for UL
386 // BA agreement. Hence, at any time the number of ICF sent is always greater than or equal to
387 // the number of QoS data frames sent.
389
390 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
391 auto phyRecvIcf = m_staMacs[0]->GetWifiPhy(linkId); // PHY receiving the ICF
392
393 auto currMainPhyLinkId = m_staMacs[0]->GetLinkForPhy(mainPhy);
394 NS_TEST_ASSERT_MSG_EQ(currMainPhyLinkId.has_value(),
395 true,
396 "Didn't find the link on which the Main PHY is operating");
397 NS_TEST_ASSERT_MSG_NE(phyRecvIcf,
398 nullptr,
399 "No PHY on the link where ICF " << m_countQoSframes << " was sent");
400
401 if (phyRecvIcf != mainPhy)
402 {
404 phyRecvIcf->GetChannelWidth(),
406 "Aux PHY that received ICF "
407 << m_countQoSframes << " is operating on a channel whose width exceeds the limit");
408 }
409
410 // the first two ICFs (before ADDBA_REQ and before first DL QoS Data) and the ICF before the
411 // ADDBA_RESP are received by the main PHY. If aux PHYs do not switch links, the ICF before
412 // the last DL QoS Data is also received by the main PHY
413 NS_TEST_EXPECT_MSG_EQ((phyRecvIcf == mainPhy),
414 (m_countIcfFrames == 1 || m_countIcfFrames == 2 ||
416 "Expecting that the ICF was received by the main PHY");
417
418 // if aux PHYs do not switch links, the main PHY is operating on its original link when
419 // the transmission of an ICF starts
420 NS_TEST_EXPECT_MSG_EQ(m_switchAuxPhy || currMainPhyLinkId == m_mainPhyId,
421 true,
422 "Main PHY is operating on an unexpected link ("
423 << +currMainPhyLinkId.value() << ", expected " << +m_mainPhyId
424 << ")");
425
426 auto txDuration =
427 WifiPhy::CalculateTxDuration(psduMap, txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand());
428
429 // check that PHYs are operating on the expected link after the reception of the ICF
430 Simulator::Schedule(txDuration + NanoSeconds(1), [=, this]() {
431 // the main PHY must be operating on the link where ICF was sent
432 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(linkId),
433 mainPhy,
434 "PHY operating on link where ICF was sent is not the main PHY");
435
436 // the behavior of Aux PHYs depends on whether they switch channel or not
437 if (m_switchAuxPhy)
438 {
439 if (mainPhy != phyRecvIcf)
440 {
441 NS_TEST_EXPECT_MSG_EQ(phyRecvIcf->IsStateSwitching(),
442 true,
443 "Aux PHY expected to switch channel");
444 }
445 Simulator::Schedule(phyRecvIcf->GetChannelSwitchDelay(), [=, this]() {
446 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetWifiPhy(*currMainPhyLinkId),
447 phyRecvIcf,
448 "The Aux PHY that received the ICF is expected to operate "
449 "on the link where Main PHY was before switching channel");
450 });
451 }
452 else
453 {
454 NS_TEST_EXPECT_MSG_EQ(phyRecvIcf->IsStateSwitching(),
455 false,
456 "Aux PHY is not expected to switch channel");
457 NS_TEST_EXPECT_MSG_EQ(phyRecvIcf->GetPhyBand(),
458 mainPhy->GetPhyBand(),
459 "The Aux PHY that received the ICF is expected to operate "
460 "on the same band as the Main PHY");
461 }
462 });
463}
464
465void
467 const WifiTxVector& txVector,
468 uint8_t linkId)
469{
470 // corrupt the first RTS frame (sent by the EMLSR client)
471 if (++m_countRtsFrames == 1)
472 {
473 auto psdu = psduMap.begin()->second;
474 m_errorModel->SetList({psdu->GetPacket()->GetUid()});
475
476 // check that when CTS timeout occurs, the main PHY is switching
478 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetWifiTxTimer().GetDelayLeft() -
479 TimeStep(1),
480 [=, this]() {
481 // store the time to complete the current channel switch at CTS timeout
482 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
483 auto toCurrSwitchEnd = mainPhy->GetDelayUntilIdle() + TimeStep(1);
484
485 Simulator::Schedule(TimeStep(1), [=, this]() {
486 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
487 true,
488 "Main PHY expected to be in SWITCHING state instead of "
489 << mainPhy->GetState()->GetState());
490
491 // If main PHY channel switch can be interrupted, the main PHY should be back
492 // operating on the preferred link after a channel switch delay. Otherwise, it
493 // will be operating on the preferred link, if SwitchAuxPhy is false, or on the
494 // link used to send the RTS, if SwitchAuxPhy is true, after the remaining
495 // channel switching time plus the channel switch delay.
498 : linkId;
499 auto delayLeft = m_resetCamStateAndInterruptSwitch
500 ? Time{0}
501 : toCurrSwitchEnd; // time to complete current switch
503 {
504 // add the time to perform another channel switch
505 delayLeft += mainPhy->GetChannelSwitchDelay();
506 }
507
508 auto totalSwitchDelay =
509 delayLeft + (mainPhy->GetChannelSwitchDelay() - toCurrSwitchEnd);
510
511 Simulator::Schedule(delayLeft - TimeStep(1), [=, this]() {
512 // check if the MSD timer was running on the link left by the main PHY
513 // before completing channel switch
514 bool msdWasRunning = m_staMacs[0]
515 ->GetEmlsrManager()
516 ->GetElapsedMediumSyncDelayTimer(m_mainPhyId)
517 .has_value();
518
519 Simulator::Schedule(TimeStep(2), [=, this]() {
520 auto id = m_staMacs[0]->GetLinkForPhy(mainPhy);
521 NS_TEST_EXPECT_MSG_EQ(id.has_value(),
522 true,
523 "Expected main PHY to operate on a link");
525 newLinkId,
526 "Main PHY is operating on an unexpected link");
527 const auto startMsd = (totalSwitchDelay > MEDIUM_SYNC_THRESHOLD);
528 const auto msdIsRunning = msdWasRunning || startMsd;
530 m_staMacs[0],
532 msdIsRunning,
533 std::string("because total switch delay was ") +
534 std::to_string(totalSwitchDelay.GetNanoSeconds()) + "ns");
535 });
536 });
537 });
538 });
539 }
540 // block transmissions on all other links at non-AP MLD side
541 std::set<uint8_t> links{0, 1, 2};
542 links.erase(linkId);
543 m_staMacs[0]->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
544 AC_BE,
546 m_apMac->GetAddress(),
547 m_staMacs[0]->GetAddress(),
548 {0},
549 links);
550}
551
552void
554{
555 NS_TEST_ASSERT_MSG_NE(m_txPsdusPos, 0, "BA agreement establishment not completed");
556
557 // Expected frame exchanges after ML setup and EML OMN exchange:
558 // 1. (DL) ICF + CTS + ADDBA_REQ + ACK
559 // 2. (UL) ADDBA_RESP + ACK
560 // 3. (DL) ICF + CTS + DATA + BA
561 // 4. (DL) ICF + CTS + DATA + BA
562 // 5. (DL) ICF + CTS + DATA + BA
563 // 6. (DL) ICF + CTS + DATA + BA
564 // 7. (UL) ADDBA_REQ + ACK
565 // 8. (DL) ICF + CTS + ADDBA_RESP + ACK
566 // 9. (UL) DATA + BA
567 // 10. (UL) RTS - CTS timeout
568 // 11. (UL) (RTS + CTS + ) DATA + BA
569
570 // frame exchange 11 is protected if SwitchAuxPhy is false or (SwitchAuxPhy is true and) the
571 // main PHY switch can be interrupted
572 bool fe11protected = !m_switchAuxPhy || m_resetCamStateAndInterruptSwitch;
573
574 NS_TEST_EXPECT_MSG_EQ(m_countIcfFrames, 6, "Unexpected number of ICFs sent");
575
576 // frame exchanges without RTS because the EMLSR client sent the initial frame through main PHY
577 const std::size_t nFrameExchNoRts = fe11protected ? 3 : 4;
578
579 const std::size_t nFrameExchWithRts = fe11protected ? 1 : 0;
580
583 m_countIcfFrames * 4 + /* frames in frame exchange with ICF */
584 nFrameExchNoRts * 2 + /* frames in frame exchange without RTS */
585 nFrameExchWithRts * 4 + /* frames in frame exchange with RTS */
586 1, /* corrupted RTS */
587 "Insufficient number of TX PSDUs");
588
589 // m_txPsdusPos points to the first ICF
590 auto psduIt = std::next(m_txPsdus.cbegin(), m_txPsdusPos);
591
592 // lambda to increase psduIt while skipping Beacon frames
593 auto nextPsdu = [&]() {
594 do
595 {
596 ++psduIt;
597 } while (psduIt != m_txPsdus.cend() &&
598 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsBeacon());
599 };
600
601 const std::size_t nFrameExchanges =
602 m_countIcfFrames + nFrameExchNoRts + nFrameExchWithRts + 1 /* corrupted RTS */;
603
604 for (std::size_t i = 1; i <= nFrameExchanges; ++i)
605 {
606 if (i == 1 || (i >= 3 && i <= 6) || i == 8 || i == 10 || (i == 11 && fe11protected))
607 {
608 // frame exchanges with protection
609 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
610 (i < 9 ? psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsTrigger()
611 : psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsRts())),
612 true,
613 "Expected a Trigger Frame (ICF)");
614 nextPsdu();
615 if (i == 10)
616 {
617 continue; // corrupted RTS
618 }
619 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
620 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsCts()),
621 true,
622 "Expected a CTS");
623 nextPsdu();
624 }
625
626 if (i == 1 || i == 2 || i == 7 || i == 8) // frame exchanges with ADDBA REQ/RESP frames
627 {
628 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
629 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsMgt()),
630 true,
631 "Expected a management frame");
632 nextPsdu();
633 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
634 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsAck()),
635 true,
636 "Expected a Normal Ack");
637 }
638 else
639 {
640 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
641 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsQosData()),
642 true,
643 "Expected a QoS Data frame");
644 nextPsdu();
645 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
646 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsBlockAck()),
647 true,
648 "Expected a BlockAck");
649 }
650 nextPsdu();
651 }
652}
653
655 : EmlsrOperationsTestBase(std::string("Check EMLSR link switching (auxPhyMaxChWidth=") +
656 std::to_string(auxPhyMaxChWidth) + "MHz )"),
657 m_auxPhyMaxChWidth(auxPhyMaxChWidth),
658 m_channelSwitchDelay(MicroSeconds(75)),
659 m_currMainPhyLinkId(0),
660 m_nextMainPhyLinkId(0)
661{
664 m_linksToEnableEmlsrOn = {0, 1, 2}; // enable EMLSR on all links right after association
665 m_mainPhyId = 1;
666 m_establishBaUl = {0};
667 m_duration = Seconds(1.0);
669}
670
671void
679
680void
682{
683 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(true));
684 Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth", UintegerValue(m_auxPhyMaxChWidth));
685 Config::SetDefault("ns3::EmlsrManager::AuxPhyMaxModClass", StringValue("EHT"));
686 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(m_channelSwitchDelay));
687
689
690 // use channels of different widths
691 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
692 {
693 mac->GetWifiPhy(0)->SetOperatingChannel(
695 mac->GetWifiPhy(1)->SetOperatingChannel(
697 mac->GetWifiPhy(2)->SetOperatingChannel(
699 }
700}
701
702void
704{
705 m_staMacs[1]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 1, 1, 2000));
706
707 // force the transmission of the packet to happen now on the given link.
708 // Multiple ScheduleNow calls are needed because Node::AddApplication() schedules a call to
709 // Application::Initialize(), which schedules a call to Application::StartApplication(), which
710 // schedules a call to PacketSocketClient::Send(), which finally generates the packet
711 Simulator::ScheduleNow([=, this]() {
712 Simulator::ScheduleNow([=, this]() {
713 Simulator::ScheduleNow([=, this]() {
714 m_staMacs[1]->GetFrameExchangeManager(linkId)->StartTransmission(
715 m_staMacs[1]->GetQosTxop(AC_BE),
716 m_staMacs[1]->GetWifiPhy(linkId)->GetChannelWidth());
717 });
718 });
719 });
720
721 // check that the other MLD started transmitting on the correct link
722 Simulator::Schedule(TimeStep(1), [=, this]() {
723 NS_TEST_EXPECT_MSG_EQ(m_staMacs[1]->GetWifiPhy(linkId)->IsStateTx(),
724 true,
725 "At time " << Simulator::Now().As(Time::NS)
726 << ", other MLD did not start transmitting on link "
727 << +linkId);
728 });
729}
730
731void
733{
734 auto currMainPhyLinkId = m_staMacs[0]->GetLinkForPhy(m_mainPhyId);
735 NS_TEST_ASSERT_MSG_EQ(currMainPhyLinkId.has_value(),
736 true,
737 "Main PHY is not operating on any link");
738 m_currMainPhyLinkId = *currMainPhyLinkId;
740
741 // request the main PHY to switch to another link
742 m_staMacs[0]->GetEmlsrManager()->SwitchMainPhy(
744 false,
746 EmlsrDlTxopIcfReceivedByAuxPhyTrace{}); // trace info not used
747
748 // the other MLD transmits a packet to the AP
750
751 // schedule another packet transmission slightly (10 us) before the end of aux PHY switch
754 this,
756
757 // first checkpoint is after that the preamble of the PPDU has been received
759}
760
761/**
762 * ┌───────────────┐
763 * [link X] │ other to AP │CP3
764 * ──────────────────────────────┴───────────────┴──────────────────────────────────────────────
765 * |------ main PHY ------| |------------------- aux PHY ---------------------
766 * .\_ _/
767 * . \_ _/
768 * . \_ _/
769 * . \_ _/
770 * [link Y] . CP1 \/ CP2
771 * .┌───────────────┐
772 * .│ other to AP │
773 * ─────────────────────────┴───────────────┴────────────────────────────────────────────────────
774 * |------------ aux PHY ----------|---------------------- main PHY ----------------------------
775 *
776 */
777
778void
780{
781 // first checkpoint is after that the preamble of the first PPDU has been received
782 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
783
784 // 1. Main PHY is switching
785 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(), true, "Main PHY is not switching");
786
787 auto auxPhy = m_staMacs[0]->GetWifiPhy(m_nextMainPhyLinkId);
788 NS_TEST_EXPECT_MSG_NE(mainPhy, auxPhy, "Main PHY is operating on an unexpected link");
789
790 // 2. Aux PHY is receiving the PHY header
791 NS_TEST_EXPECT_MSG_EQ(auxPhy->GetInfoIfRxingPhyHeader().has_value(),
792 true,
793 "Aux PHY is not receiving a PHY header");
794
795 // 3. Main PHY dropped the preamble because it is switching
796 NS_TEST_EXPECT_MSG_EQ(mainPhy->GetInfoIfRxingPhyHeader().has_value(),
797 false,
798 "Main PHY is receiving a PHY header");
799
800 // 4. Channel access manager on destination link (Y) has been notified of CCA busy, but not
801 // until the end of transmission (main PHY dropped the preamble and notified CCA busy until
802 // end of transmission but the channel access manager on link Y does not yet have a listener
803 // attached to the main PHY; aux PHY notified CCA busy until the end of the PHY header field
804 // being received)
805 const auto caManager = m_staMacs[0]->GetChannelAccessManager(m_nextMainPhyLinkId);
806 const auto endTxTime = m_staMacs[1]->GetChannelAccessManager(m_nextMainPhyLinkId)->m_lastTxEnd;
807 NS_TEST_ASSERT_MSG_EQ(caManager->m_lastBusyEnd.contains(WIFI_CHANLIST_PRIMARY),
808 true,
809 "No CCA information for primary20 channel");
811 caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
813 "ChannelAccessManager on destination link not notified of CCA busy");
815 caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
816 endTxTime,
817 "ChannelAccessManager on destination link notified of CCA busy until end of transmission");
818
819 // second checkpoint is after that the main PHY completed the link switch
820 Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1),
822 this);
823}
824
825void
827{
828 // second checkpoint is after that the main PHY completed the link switch. The channel access
829 // manager on destination link (Y) is expected to be notified by the main PHY that medium is
830 // busy until the end of the ongoing transmission
831 const auto caManager = m_staMacs[0]->GetChannelAccessManager(m_nextMainPhyLinkId);
832 const auto endTxTime = m_staMacs[1]->GetChannelAccessManager(m_nextMainPhyLinkId)->m_lastTxEnd;
833 NS_TEST_ASSERT_MSG_EQ(caManager->m_lastBusyEnd.contains(WIFI_CHANLIST_PRIMARY),
834 true,
835 "No CCA information for primary20 channel");
837 caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
839 "ChannelAccessManager on destination link not notified of CCA busy");
840 NS_TEST_EXPECT_MSG_GT_OR_EQ(caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
841 endTxTime,
842 "ChannelAccessManager on destination link not notified of CCA busy "
843 "until end of transmission");
844
845 // third checkpoint is after that the aux PHY completed the link switch
847}
848
849void
851{
852 // third checkpoint is after that the aux PHY completed the link switch. The channel access
853 // manager on source link (X) is expected to be notified by the aux PHY that medium is
854 // busy until the end of the ongoing transmission (even if the aux PHY was not listening to
855 // link X when transmission started, its interface on link X recorded the transmission)
856 const auto caManager = m_staMacs[0]->GetChannelAccessManager(m_currMainPhyLinkId);
857 const auto endTxTime = m_staMacs[1]->GetChannelAccessManager(m_currMainPhyLinkId)->m_lastTxEnd;
858 NS_TEST_ASSERT_MSG_EQ(caManager->m_lastBusyEnd.contains(WIFI_CHANLIST_PRIMARY),
859 true,
860 "No CCA information for primary20 channel");
861 NS_TEST_EXPECT_MSG_GT_OR_EQ(caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
863 "ChannelAccessManager on source link not notified of CCA busy");
864 NS_TEST_EXPECT_MSG_GT_OR_EQ(caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
865 endTxTime,
866 "ChannelAccessManager on source link not notified of CCA busy "
867 "until end of transmission");
868}
869
870SingleLinkEmlsrTest::SingleLinkEmlsrTest(bool switchAuxPhy, bool auxPhyTxCapable)
872 "Check EMLSR single link operation (switchAuxPhy=" + std::to_string(switchAuxPhy) +
873 ", auxPhyTxCapable=" + std::to_string(auxPhyTxCapable) + ")"),
874 m_switchAuxPhy(switchAuxPhy),
875 m_auxPhyTxCapable(auxPhyTxCapable)
876{
877 m_mainPhyId = 0;
882
883 // channel switch delay will be also set to 64 us
886 m_establishBaDl = {0};
887 m_establishBaUl = {0};
888 m_duration = Seconds(0.5);
889}
890
891void
893{
894 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(64)));
895 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(m_switchAuxPhy));
896 Config::SetDefault("ns3::EmlsrManager::AuxPhyTxCapable", BooleanValue(m_auxPhyTxCapable));
897
899}
900
901void
903 uint8_t phyId,
904 WifiConstPsduMap psduMap,
905 WifiTxVector txVector,
906 double txPowerW)
907{
908 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
909
910 const auto psdu = psduMap.cbegin()->second;
911 const auto& hdr = psdu->GetHeader(0);
912
913 // nothing to do in case of Beacon and CF-End frames
914 if (hdr.IsBeacon() || hdr.IsCfEnd())
915 {
916 return;
917 }
918
919 auto linkId = mac->GetLinkForPhy(phyId);
920 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(),
921 true,
922 "PHY " << +phyId << " is not operating on any link");
923 NS_TEST_EXPECT_MSG_EQ(+linkId.value(), 0, "TX occurred on unexpected link " << +linkId.value());
924
925 if (m_eventIt != m_events.cend())
926 {
927 // check that the expected frame is being transmitted
929 hdr.GetType(),
930 "Unexpected MAC header type for frame #"
931 << std::distance(m_events.cbegin(), m_eventIt));
932 // perform actions/checks, if any
933 if (m_eventIt->func)
934 {
935 m_eventIt->func(psdu, txVector);
936 }
937
938 ++m_eventIt;
939 }
940}
941
942void
944{
945 // lambda to check that AP MLD started the transition delay timer after the TX/RX of given frame
946 auto checkTransDelay = [this](Ptr<const WifiPsdu> psdu,
947 const WifiTxVector& txVector,
948 bool testUnblockedForOtherReasons,
949 const std::string& frameStr) {
950 const auto txDuration = WifiPhy::CalculateTxDuration(psdu->GetSize(),
951 txVector,
952 m_apMac->GetWifiPhy(0)->GetPhyBand());
953 Simulator::Schedule(txDuration + MicroSeconds(1), /* to account for propagation delay */
955 this,
956 m_apMac,
957 m_staMacs[0]->GetAddress(),
958 0,
959 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
960 true,
961 "Checking that AP MLD blocked transmissions to single link EMLSR "
962 "client after " +
963 frameStr,
964 testUnblockedForOtherReasons);
965 };
966
967 // expected sequence of transmitted frames
969 m_events.emplace_back(WIFI_MAC_CTL_ACK);
971 m_events.emplace_back(WIFI_MAC_CTL_ACK);
972
973 // EML OMN sent by EMLSR client
974 m_events.emplace_back(WIFI_MAC_MGT_ACTION,
975 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
976 // check that the address of the EMLSR client is seen as an MLD
977 // address
979 m_apMac->GetWifiRemoteStationManager(0)
980 ->GetMldAddress(m_staMacs[0]->GetAddress())
981 .has_value(),
982 true,
983 "Expected the EMLSR client address to be seen as an MLD address");
984 });
985 m_events.emplace_back(WIFI_MAC_CTL_ACK);
986 // EML OMN sent by AP MLD, protected by ICF
987 m_events.emplace_back(WIFI_MAC_CTL_TRIGGER);
988 m_events.emplace_back(WIFI_MAC_CTL_CTS);
989 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
990 m_events.emplace_back(WIFI_MAC_CTL_ACK,
991 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
992 // check that EMLSR mode has been enabled on link 0 of EMLSR client
994 m_staMacs[0]->IsEmlsrLink(0),
995 true,
996 "Expected EMLSR mode to be enabled on link 0 of EMLSR client");
997 });
998
999 // Establishment of BA agreement for downlink direction
1000
1001 // ADDBA REQUEST sent by AP MLD (protected by ICF)
1002 m_events.emplace_back(WIFI_MAC_CTL_TRIGGER);
1003 m_events.emplace_back(WIFI_MAC_CTL_CTS);
1004 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
1005 m_events.emplace_back(WIFI_MAC_CTL_ACK,
1006 [=](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
1007 // check that transition delay is started after reception of Ack
1008 checkTransDelay(psdu, txVector, false, "DL ADDBA REQUEST");
1009 });
1010
1011 // ADDBA RESPONSE sent by EMLSR client (no RTS because it is sent by main PHY)
1012 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
1013 m_events.emplace_back(WIFI_MAC_CTL_ACK,
1014 [=](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
1015 // check that transition delay is started after reception of Ack
1016 checkTransDelay(psdu, txVector, true, "DL ADDBA RESPONSE");
1017 });
1018
1019 // Downlink QoS data frame that triggered BA agreement establishment
1020 m_events.emplace_back(WIFI_MAC_CTL_TRIGGER);
1021 m_events.emplace_back(WIFI_MAC_CTL_CTS);
1022 m_events.emplace_back(WIFI_MAC_QOSDATA);
1023 m_events.emplace_back(WIFI_MAC_CTL_BACKRESP,
1024 [=](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
1025 // check that transition delay is started after reception of BlockAck
1026 checkTransDelay(psdu, txVector, true, "DL QoS Data");
1027 });
1028
1029 // Establishment of BA agreement for uplink direction
1030
1031 // ADDBA REQUEST sent by EMLSR client (no RTS because it is sent by main PHY)
1032 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
1033 m_events.emplace_back(WIFI_MAC_CTL_ACK,
1034 [=](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
1035 // check that transition delay is started after reception of Ack
1036 checkTransDelay(psdu, txVector, false, "UL ADDBA REQUEST");
1037 });
1038 // ADDBA RESPONSE sent by AP MLD (protected by ICF)
1039 m_events.emplace_back(WIFI_MAC_CTL_TRIGGER);
1040 m_events.emplace_back(WIFI_MAC_CTL_CTS);
1041 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
1042 m_events.emplace_back(WIFI_MAC_CTL_ACK,
1043 [=](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
1044 // check that transition delay is started after reception of Ack
1045 checkTransDelay(psdu, txVector, true, "UL ADDBA RESPONSE");
1046 });
1047
1048 // Uplink QoS data frame that triggered BA agreement establishment
1049 m_events.emplace_back(WIFI_MAC_QOSDATA);
1050 m_events.emplace_back(WIFI_MAC_CTL_BACKRESP,
1051 [=](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
1052 // check that transition delay is started after reception of BlockAck
1053 checkTransDelay(psdu, txVector, true, "UL QoS Data");
1054 });
1055
1056 m_eventIt = m_events.cbegin();
1057
1060
1061 NS_TEST_EXPECT_MSG_EQ((m_eventIt == m_events.cend()), true, "Not all events took place");
1062
1064}
1065
1067 : EmlsrOperationsTestBase("Check ICF reception while main PHY is switching")
1068{
1069 m_mainPhyId = 0;
1070 m_linksToEnableEmlsrOn = {0, 1, 2};
1071 m_nEmlsrStations = 1;
1073
1074 // channel switch delay will be also set to 64 us
1077 m_establishBaDl = {0, 3};
1078 m_establishBaUl = {0, 3};
1079 m_duration = Seconds(0.5);
1080}
1081
1082void
1084{
1085 // channel switch delay will be modified during test scenarios
1086 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(64)));
1087 Config::SetDefault("ns3::WifiPhy::NotifyMacHdrRxEnd", BooleanValue(true));
1088 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(false));
1089 Config::SetDefault("ns3::EmlsrManager::AuxPhyTxCapable", BooleanValue(false));
1090 // AP MLD transmits both TID 0 and TID 3 on link 1
1091 Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingDl", StringValue("0,3 1"));
1092 // EMLSR client transmits TID 0 on link 1 and TID 3 on link 2
1093 Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingUl",
1094 StringValue("0 1; 3 " + std::to_string(m_linkIdForTid3)));
1095
1097
1098 m_staMacs[0]->TraceConnectWithoutContext(
1099 "EmlsrLinkSwitch",
1101
1102 for (uint8_t i = 0; i < m_staMacs[0]->GetDevice()->GetNPhys(); ++i)
1103 {
1104 m_bands.at(i) = m_staMacs[0]->GetDevice()->GetPhy(i)->GetBand(MHz_u{20}, 0);
1105 }
1106}
1107
1108void
1110{
1111 for (const auto linkId : mac->GetLinkIds())
1112 {
1113 auto phy = DynamicCast<SpectrumWifiPhy>(mac->GetWifiPhy(linkId));
1114 NS_TEST_ASSERT_MSG_NE(phy, nullptr, "No PHY on link " << +linkId);
1115 const auto txPower = phy->GetPower(1) + phy->GetTxGain();
1116
1117 auto psd = Create<SpectrumValue>(phy->GetCurrentInterface()->GetRxSpectrumModel());
1118 *psd = txPower;
1119
1120 auto spectrumSignalParams = Create<SpectrumSignalParameters>();
1121 spectrumSignalParams->duration = duration;
1122 spectrumSignalParams->txPhy = phy->GetCurrentInterface();
1123 spectrumSignalParams->txAntenna = phy->GetAntenna();
1124 spectrumSignalParams->psd = psd;
1125
1126 phy->StartRx(spectrumSignalParams, phy->GetCurrentInterface());
1127 }
1128}
1129
1130void
1132 uint8_t linkId,
1133 Time duration)
1134{
1135 for (const auto& phy : m_staMacs[0]->GetDevice()->GetPhys())
1136 {
1137 // ignore the PHY that is transmitting
1138 if (m_staMacs[0]->GetLinkForPhy(phy) == linkId)
1139 {
1140 continue;
1141 }
1142
1143 PointerValue ptr;
1144 phy->GetAttribute("InterferenceHelper", ptr);
1145 auto interferenceHelper = ptr.Get<InterferenceHelper>();
1146
1147 // we need to check that all the PHY interfaces recorded the in-device interference,
1148 // hence we consider a 20 MHz sub-band of the frequency channels of all the links
1149 for (uint8_t i = 0; i < m_staMacs[0]->GetNLinks(); ++i)
1150 {
1151 auto energyDuration =
1152 interferenceHelper->GetEnergyDuration(DbmToW(phy->GetCcaEdThreshold()),
1153 m_bands.at(i));
1154
1156 energyDuration,
1157 duration,
1158 m_testStr << ", " << frameTypeStr << ": Unexpected energy duration for PHY "
1159 << +phy->GetPhyId() << " in the band corresponding to link " << +i);
1160 }
1161 }
1162}
1163
1164void
1166 uint8_t phyId,
1167 WifiConstPsduMap psduMap,
1168 WifiTxVector txVector,
1169 double txPowerW)
1170{
1171 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
1172
1173 const auto psdu = psduMap.cbegin()->second;
1174 const auto& hdr = psdu->GetHeader(0);
1175
1176 // nothing to do before setup is completed
1177 if (!m_setupDone)
1178 {
1179 return;
1180 }
1181
1182 auto linkId = mac->GetLinkForPhy(phyId);
1183 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(),
1184 true,
1185 "PHY " << +phyId << " is not operating on any link");
1186
1187 if (!m_events.empty())
1188 {
1189 // check that the expected frame is being transmitted
1190 NS_TEST_EXPECT_MSG_EQ(m_events.front().hdrType,
1191 hdr.GetType(),
1192 "Unexpected MAC header type for frame #" << ++m_processedEvents);
1193 // perform actions/checks, if any
1194 if (m_events.front().func)
1195 {
1196 m_events.front().func(psdu, txVector, linkId.value());
1197 }
1198
1199 m_events.pop_front();
1200 }
1201}
1202
1203void
1209
1210void
1212{
1215
1216 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
1217
1219}
1220
1221void
1223 Ptr<WifiPhy> phy,
1224 bool connected)
1225{
1226 if (!m_setupDone)
1227 {
1228 return;
1229 }
1230
1231 if (!connected)
1232 {
1233 const auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
1234 NS_LOG_DEBUG("Main PHY leaving link " << +linkId << ", switch delay "
1235 << mainPhy->GetChannelSwitchDelay().As(Time::US)
1236 << "\n");
1238 m_switchTo.reset();
1239 }
1240 else
1241 {
1242 NS_LOG_DEBUG((phy->GetPhyId() == m_mainPhyId ? "Main" : "Aux")
1243 << " PHY connected to link " << +linkId << "\n");
1244 if (phy->GetPhyId() == m_mainPhyId)
1245 {
1247 m_switchFrom.reset();
1248 }
1249 }
1250}
1251
1252void
1254{
1255 const auto useMacHdrInfo = ((m_testIndex & 0b001) != 0);
1256 const auto interruptSwitch = ((m_testIndex & 0b010) != 0);
1257 const auto switchToOtherLink = ((m_testIndex & 0b100) != 0);
1258
1259 const auto keepMainPhyAfterDlTxop = useMacHdrInfo;
1260
1261 m_staMacs[0]->GetEmlsrManager()->SetAttribute("UseNotifiedMacHdr", BooleanValue(useMacHdrInfo));
1262 auto advEmlsrMgr = DynamicCast<AdvancedEmlsrManager>(m_staMacs[0]->GetEmlsrManager());
1263 NS_TEST_ASSERT_MSG_NE(advEmlsrMgr, nullptr, "Advanced EMLSR Manager required");
1264 advEmlsrMgr->SetAttribute("InterruptSwitch", BooleanValue(interruptSwitch));
1265 advEmlsrMgr->SetAttribute("KeepMainPhyAfterDlTxop", BooleanValue(keepMainPhyAfterDlTxop));
1266
1267 m_testStr = "SwitchToOtherLink=" + std::to_string(switchToOtherLink) +
1268 ", InterruptSwitch=" + std::to_string(interruptSwitch) +
1269 ", UseMacHdrInfo=" + std::to_string(useMacHdrInfo) +
1270 ", KeepMainPhyAfterDlTxop=" + std::to_string(keepMainPhyAfterDlTxop) +
1271 ", ChannelSwitchDurationIdx=" + std::to_string(m_csdIndex);
1272 NS_LOG_INFO("Starting test: " << m_testStr << "\n");
1273
1274 // generate noise on all the links of the AP MLD and the EMLSR client, so as to align the EDCA
1275 // backoff boundaries
1276 Simulator::Schedule(MilliSeconds(3), [=, this]() {
1279 });
1280
1281 // wait some more time to ensure that backoffs count down to zero and then generate a packet
1282 // at the AP MLD and a packet at the EMLSR client. AP MLD and EMLSR client are expected to get
1283 // access at the same time because backoff counter is zero and EDCA boundaries are aligned
1284 Simulator::Schedule(MilliSeconds(5), [=, this]() {
1285 uint8_t prio = (switchToOtherLink ? 3 : 0);
1286 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 0, 1, 500, prio));
1287 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1288 GetApplication(UPLINK, 0, 1, 500, prio));
1289 });
1290
1291 m_switchFrom.reset();
1292 m_switchTo.reset();
1293
1294 m_events.emplace_back(
1296 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1297 const auto phyHdrDuration = WifiPhy::CalculatePhyPreambleAndHeaderDuration(txVector);
1298 const auto txDuration =
1300 txVector,
1301 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
1302 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
1303
1304 // compute channel switch delay based on the scenario to test
1305 Time channelSwitchDelay{0};
1306 const auto margin = MicroSeconds(2);
1307
1308 switch (m_csdIndex)
1309 {
1311 channelSwitchDelay = MicroSeconds(1);
1312 break;
1313 case BEFORE_PHY_HDR_END:
1314 channelSwitchDelay = phyHdrDuration - margin;
1315 break;
1316 case BEFORE_MAC_HDR_END:
1317 channelSwitchDelay = phyHdrDuration + margin;
1318 break;
1320 channelSwitchDelay = txDuration - m_paddingDelay.at(0) - margin;
1321 break;
1322 case BEFORE_PADDING_END:
1323 channelSwitchDelay = txDuration - m_paddingDelay.at(0) + margin;
1324 break;
1325 default:;
1326 }
1327
1328 NS_TEST_ASSERT_MSG_EQ(channelSwitchDelay.IsStrictlyPositive(),
1329 true,
1330 m_testStr << ": Channel switch delay is not strictly positive ("
1331 << channelSwitchDelay.As(Time::US) << ")");
1332 NS_TEST_ASSERT_MSG_LT(channelSwitchDelay,
1333 m_paddingDelay.at(0),
1334 m_testStr
1335 << ": Channel switch delay is greater than padding delay");
1336 // set channel switch delay
1337 mainPhy->SetAttribute("ChannelSwitchDelay", TimeValue(channelSwitchDelay));
1338
1339 const auto startTx = Simulator::Now();
1340
1341 // check that main PHY has started switching
1342 Simulator::ScheduleNow([=, this]() {
1344 true,
1345 m_testStr << ": Main PHY did not start switching");
1347 +m_mainPhyId,
1348 m_testStr << ": Main PHY did not left the preferred link");
1350 startTx,
1351 m_testStr
1352 << ": Main PHY did not start switching at ICF TX start");
1353 });
1354
1355 // check what happens after channel switch is completed
1356 Simulator::Schedule(channelSwitchDelay + TimeStep(1), [=, this]() {
1357 // sanity check that the channel switch delay was computed correctly
1358 auto auxPhy = m_staMacs[0]->GetWifiPhy(linkId);
1359 auto fem = m_staMacs[0]->GetFrameExchangeManager(linkId);
1360 switch (m_csdIndex)
1361 {
1362 case BEFORE_PADDING_END:
1365 startTx + txDuration - m_paddingDelay.at(0),
1366 m_testStr << ": Channel switch terminated before padding start");
1367 [[fallthrough]];
1369 if (useMacHdrInfo)
1370 {
1371 NS_TEST_EXPECT_MSG_EQ(fem->GetReceivedMacHdr().has_value(),
1372 true,
1373 m_testStr << ": Channel switch terminated before "
1374 "MAC header info is received");
1375 }
1376 [[fallthrough]];
1377 case BEFORE_MAC_HDR_END:
1378 NS_TEST_EXPECT_MSG_EQ(fem->GetOngoingRxInfo().has_value(),
1379 true,
1380 m_testStr << ": Channel switch terminated before "
1381 "receiving RXSTART indication");
1382 break;
1383 case BEFORE_PHY_HDR_END:
1384 NS_TEST_EXPECT_MSG_EQ(auxPhy->GetInfoIfRxingPhyHeader().has_value(),
1385 true,
1386 m_testStr << ": Expected to be receiving the PHY header");
1387 break;
1389 NS_TEST_EXPECT_MSG_EQ(auxPhy->GetTimeToPreambleDetectionEnd().has_value(),
1390 true,
1391 m_testStr
1392 << ": Expected to be in preamble detection period");
1393 default:
1394 NS_ABORT_MSG("Unexpected channel switch duration index");
1395 }
1396
1397 // if the main PHY switched to the same link on which the ICF is being received,
1398 // connecting the main PHY to the link is postponed until the end of the ICF, hence
1399 // the main PHY is not operating on any link at this time;
1400 // if the main PHY switched to another link, it was connected to that link but
1401 // the UL TXOP did not start because, at the end of the NAV and CCA busy in the last
1402 // PIFS check, it was detected that a frame which could be an ICF was being received
1403 // on another link)
1404 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetLinkForPhy(m_mainPhyId).has_value(),
1405 switchToOtherLink,
1406 m_testStr
1407 << ": Main PHY not expected to be connected to a link");
1408
1409 if (switchToOtherLink)
1410 {
1412 +m_staMacs[0]->GetLinkForPhy(m_mainPhyId).value(),
1414 m_testStr << ": Main PHY did not left the link on which TID 3 is mapped");
1415 }
1416 });
1417
1418 // check what happens when the ICF ends
1419 Simulator::Schedule(txDuration + TimeStep(1), [=, this]() {
1420 // if the main PHY switched to the same link on which the ICF has been received,
1421 // it has now been connected to that link; if the main PHY switched to another
1422 // link and there was not enough time for the main PHY to start switching to the
1423 // link on which the ICF has been received at the start of the padding, the ICF
1424 // has been dropped and the main PHY stayed on the preferred link
1425
1426 const auto id = m_staMacs[0]->GetLinkForPhy(m_mainPhyId);
1427 NS_TEST_EXPECT_MSG_EQ(id.has_value(),
1428 true,
1429 m_testStr << ": Main PHY expected to be connected to a link");
1430 NS_TEST_EXPECT_MSG_EQ(+id.value(),
1431 +linkId,
1432 m_testStr << ": Main PHY connected to an unexpected link");
1433
1435 true,
1436 m_testStr << ": Main PHY was not connected to a link");
1438 +linkId,
1439 m_testStr
1440 << ": Main PHY was not connected to the expected link");
1442 Simulator::Now() - TimeStep(1),
1443 m_testStr << ": Main PHY was not connected at ICF TX end");
1444 });
1445 });
1446
1447 m_events.emplace_back(
1449 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1450 const auto id = m_staMacs[0]->GetLinkForPhy(m_mainPhyId);
1451 NS_TEST_EXPECT_MSG_EQ(id.has_value(),
1452 true,
1453 m_testStr << ": Main PHY expected to be connected to a link");
1454 NS_TEST_EXPECT_MSG_EQ(+id.value(),
1455 +linkId,
1456 m_testStr
1457 << ": Main PHY expected to be connected to same link as ICF");
1458 Simulator::ScheduleNow([=, this]() {
1459 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId)->IsStateTx(),
1460 true,
1461 m_testStr << ": Main PHY expected to be transmitting");
1462 });
1463
1464 const auto txDuration =
1466 txVector,
1467 m_staMacs[0]->GetWifiPhy(linkId)->GetPhyBand());
1468
1469 Simulator::ScheduleNow([=, this]() {
1470 CheckInDeviceInterference(m_testStr + ", CTS", linkId, txDuration);
1471 });
1472 });
1473
1474 m_events.emplace_back(WIFI_MAC_QOSDATA);
1475 m_events.emplace_back(
1477 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1478 const auto txDuration =
1480 txVector,
1481 m_staMacs[0]->GetWifiPhy(linkId)->GetPhyBand());
1482
1483 Simulator::ScheduleNow([=, this]() {
1484 CheckInDeviceInterference(m_testStr + ", ACK", linkId, txDuration);
1485 });
1486 // check the KeepMainPhyAfterDlTxop attribute
1487 Simulator::Schedule(txDuration + TimeStep(1), [=, this]() {
1488 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
1489 auto shouldSwitch = (!keepMainPhyAfterDlTxop || switchToOtherLink);
1490 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
1491 shouldSwitch,
1492 m_testStr << ": Main PHY should "
1493 << (shouldSwitch ? "" : "not")
1494 << " be switching back after DL TXOP end");
1495 });
1496 });
1497
1498 // Uplink TXOP
1499 m_events.emplace_back(
1501 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1502 const auto txDuration =
1504 txVector,
1505 m_staMacs[0]->GetWifiPhy(linkId)->GetPhyBand());
1506
1507 Simulator::ScheduleNow([=, this]() {
1508 CheckInDeviceInterference(m_testStr + ", QoS Data", linkId, txDuration);
1509 });
1510 });
1511
1512 m_events.emplace_back(
1514 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1515 const auto txDuration =
1517 txVector,
1518 m_staMacs[0]->GetWifiPhy(linkId)->GetPhyBand());
1519 // check that main PHY switches back after UL TXOP
1520 Simulator::Schedule(txDuration + TimeStep(1), [=, this]() {
1521 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
1523 mainPhy->IsStateSwitching(),
1524 true,
1525 m_testStr << ": Main PHY should be switching back after UL TXOP end");
1526 });
1527 // Continue with the next test scenario
1528 Simulator::Schedule(MilliSeconds(2), [=, this]() {
1529 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
1530 m_csdIndex = static_cast<ChannelSwitchEnd>(static_cast<uint8_t>(m_csdIndex) + 1);
1531 if (m_csdIndex == CSD_COUNT)
1532 {
1533 ++m_testIndex;
1535 }
1536
1537 if (m_testIndex < 8)
1538 {
1539 RunOne();
1540 }
1541 });
1542 });
1543}
1544
1546 : EmlsrOperationsTestBase("Check handling of the switch main PHY back timer")
1547{
1548 m_mainPhyId = 2;
1549 m_linksToEnableEmlsrOn = {0, 1, 2};
1550 m_nEmlsrStations = 1;
1552
1553 // channel switch delay will be also set to 64 us
1556 m_establishBaDl = {0};
1557 m_establishBaUl = {0, 4};
1558 m_duration = Seconds(0.5);
1559}
1560
1561void
1563{
1564 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(64)));
1565 Config::SetDefault("ns3::WifiPhy::NotifyMacHdrRxEnd", BooleanValue(true));
1566 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(false));
1567 Config::SetDefault("ns3::EmlsrManager::AuxPhyTxCapable", BooleanValue(false));
1568 // Use only link 1 for DL and UL traffic
1569 std::string mapping =
1570 "0 " + std::to_string(m_linkIdForTid0) + "; 4 " + std::to_string(m_linkIdForTid4);
1571 Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingDl", StringValue(mapping));
1572 Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingUl", StringValue(mapping));
1573 Config::SetDefault("ns3::EmlsrManager::AuxPhyMaxModClass", StringValue("HT"));
1574 Config::SetDefault("ns3::AdvancedEmlsrManager::UseAuxPhyCca", BooleanValue(true));
1575
1577
1580 hdr.SetAddr2(m_apMac->GetFrameExchangeManager(m_linkIdForTid0)->GetAddress());
1581 hdr.SetAddr3(m_apMac->GetAddress());
1582 hdr.SetDsFrom();
1583 hdr.SetDsNotTo();
1584 hdr.SetQosTid(0);
1585
1587}
1588
1589void
1591 uint8_t phyId,
1592 WifiConstPsduMap psduMap,
1593 WifiTxVector txVector,
1594 double txPowerW)
1595{
1596 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
1597
1598 const auto psdu = psduMap.cbegin()->second;
1599 const auto& hdr = psdu->GetHeader(0);
1600
1601 // nothing to do before setup is completed
1602 if (!m_setupDone)
1603 {
1604 return;
1605 }
1606
1607 auto linkId = mac->GetLinkForPhy(phyId);
1608 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(),
1609 true,
1610 "PHY " << +phyId << " is not operating on any link");
1611
1612 if (!m_events.empty())
1613 {
1614 // check that the expected frame is being transmitted
1615 NS_TEST_EXPECT_MSG_EQ(m_events.front().hdrType,
1616 hdr.GetType(),
1617 "Unexpected MAC header type for frame #" << ++m_processedEvents);
1618 // perform actions/checks, if any
1619 if (m_events.front().func)
1620 {
1621 m_events.front().func(psdu, txVector, linkId.value());
1622 }
1623
1624 m_events.pop_front();
1625 }
1626}
1627
1628void
1634
1635void
1637{
1640
1641 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
1642
1644}
1645
1646void
1648 const EmlsrMainPhySwitchTrace& info)
1649{
1651
1652 if (!m_setupDone)
1653 {
1654 return;
1655 }
1656
1657 if (!m_dlPktDone && info.GetName() == "UlTxopAuxPhyNotTxCapable")
1658 {
1659 NS_LOG_INFO("Main PHY starts switching\n");
1660 const auto delay =
1662 ? Time{0}
1663 : MicroSeconds(30); // greater than duration of PHY header of non-HT PPDU
1664 Simulator::Schedule(delay,
1665 [=, this]() { m_apMac->GetQosTxop(AC_BE)->Queue(m_bcastFrame); });
1666 return;
1667 }
1668
1669 // lambda to generate a frame with TID 4 and to handle the corresponding frames
1670 auto genTid4Frame = [=, this]() {
1671 m_dlPktDone = true;
1672
1673 // in 5 microseconds, while still switching, generate a packet with TID 4, which causes a
1674 // channel access request on link 0; if switching can be interrupted, the main PHY starts
1675 // switching to link 0 as soon as channel access is gained on link 0
1676 Simulator::Schedule(MicroSeconds(5), [=, this]() {
1677 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1678 GetApplication(UPLINK, 0, 1, 500, 4));
1679 // channel access can be obtained within a slot due to slot alignment
1680 Simulator::Schedule(m_apMac->GetWifiPhy(m_linkIdForTid4)->GetSlot(), [=, this]() {
1681 auto advEmlsrMgr =
1682 DynamicCast<AdvancedEmlsrManager>(m_staMacs[0]->GetEmlsrManager());
1683
1684 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.disconnected,
1685 true,
1686 "Expected the main PHY to be switching");
1687 NS_TEST_EXPECT_MSG_EQ(
1688 +advEmlsrMgr->m_mainPhySwitchInfo.to,
1689 +(advEmlsrMgr->m_interruptSwitching ? m_linkIdForTid4 : m_mainPhyId),
1690 "Main PHY is switching to wrong link");
1691 });
1692 });
1694 };
1695
1697 info.GetName() == "TxopNotGainedOnAuxPhyLink")
1698 {
1699 NS_LOG_INFO("Main PHY switches back\n");
1700
1701 const auto& traceInfo = static_cast<const EmlsrSwitchMainPhyBackTrace&>(info);
1702
1703 switch (static_cast<TestScenario>(m_testIndex))
1704 {
1707 NS_TEST_EXPECT_MSG_EQ((traceInfo.elapsed.IsStrictlyPositive() &&
1708 traceInfo.elapsed < m_switchMainPhyBackDelay),
1709 true,
1710 "Unexpected value for the elapsed field");
1711 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.has_value(),
1712 true,
1713 "earlySwitchReason should hold a value");
1714 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.value(),
1715 WifiExpectedAccessReason::RX_END,
1716 "Unexpected earlySwitchReason value");
1717 NS_TEST_EXPECT_MSG_EQ(traceInfo.isSwitching, true, "Unexpected value for isSwitching");
1718 break;
1719
1721 NS_TEST_EXPECT_MSG_EQ((traceInfo.elapsed.IsStrictlyPositive() &&
1722 traceInfo.elapsed < m_switchMainPhyBackDelay),
1723 true,
1724 "Unexpected value for the elapsed field");
1725 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.has_value(),
1726 true,
1727 "earlySwitchReason should hold a value");
1728 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.value(),
1729 WifiExpectedAccessReason::BUSY_END,
1730 "Unexpected earlySwitchReason value");
1731 NS_TEST_EXPECT_MSG_EQ(traceInfo.isSwitching, false, "Unexpected value for isSwitching");
1732 break;
1733
1735 NS_TEST_EXPECT_MSG_EQ((traceInfo.elapsed.IsStrictlyPositive() &&
1736 traceInfo.elapsed < m_switchMainPhyBackDelay),
1737 true,
1738 "Unexpected value for the elapsed field");
1739 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.has_value(),
1740 true,
1741 "earlySwitchReason should hold a value");
1742 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.value(),
1743 WifiExpectedAccessReason::RX_END,
1744 "Unexpected earlySwitchReason value");
1745 NS_TEST_EXPECT_MSG_EQ(traceInfo.isSwitching, false, "Unexpected value for isSwitching");
1746 break;
1747
1749 NS_TEST_EXPECT_MSG_EQ((traceInfo.elapsed.IsStrictlyPositive() &&
1750 traceInfo.elapsed >= m_switchMainPhyBackDelay),
1751 true,
1752 "Unexpected value for the elapsed field");
1753 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.has_value(),
1754 true,
1755 "earlySwitchReason should hold a value");
1756 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.value(),
1757 WifiExpectedAccessReason::BACKOFF_END,
1758 "Unexpected earlySwitchReason value");
1759 NS_TEST_EXPECT_MSG_EQ(traceInfo.isSwitching, false, "Unexpected value for isSwitching");
1760 break;
1761
1763 NS_TEST_EXPECT_MSG_EQ((traceInfo.elapsed.IsStrictlyPositive() &&
1764 traceInfo.elapsed < m_switchMainPhyBackDelay),
1765 true,
1766 "Unexpected value for the elapsed field");
1767 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.has_value(),
1768 true,
1769 "earlySwitchReason should hold a value");
1770 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.value(),
1771 WifiExpectedAccessReason::BACKOFF_END,
1772 "Unexpected earlySwitchReason value");
1773 NS_TEST_EXPECT_MSG_EQ(traceInfo.isSwitching, false, "Unexpected value for isSwitching");
1774 break;
1775
1776 default:
1777 NS_TEST_ASSERT_MSG_EQ(true, false, "Unexpected scenario: " << +m_testIndex);
1778 }
1779
1780 genTid4Frame();
1781 }
1782
1783 if (m_expectedMainPhySwitchBackTime == Simulator::Now() && info.GetName() == "TxopEnded")
1784 {
1785 NS_LOG_INFO("Main PHY switches back\n");
1786
1788 +static_cast<uint8_t>(NON_HT_PPDU_DONT_USE_MAC_HDR),
1789 "Unexpected TxopEnded reason for switching main PHY back");
1790
1791 genTid4Frame();
1792 }
1793}
1794
1795void
1797{
1798 const auto testIndex = static_cast<TestScenario>(m_testIndex);
1799 std::list<Events> events;
1800
1801 events.emplace_back(
1803 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1804 NS_TEST_EXPECT_MSG_EQ(+linkId,
1806 "Unicast frame with TID 4 transmitted on wrong link");
1807 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
1808 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1809 "Unexpected RA for the unicast frame with TID 4");
1810 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
1811 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
1812 "Unexpected TA for the unicast frame with TID 4");
1813 NS_TEST_EXPECT_MSG_EQ(+(*psdu->GetTids().cbegin()),
1814 4,
1815 "Expected a unicast frame with TID 4");
1816 // if switching can be interrupted, the frame with TID 4 is transmitted as soon as
1817 // the main PHY completes the switching to link 0
1818 if (auto advEmlsrMgr =
1819 DynamicCast<AdvancedEmlsrManager>(m_staMacs[0]->GetEmlsrManager());
1820 advEmlsrMgr->m_interruptSwitching)
1821 {
1822 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
1823 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.start +
1824 mainPhy->GetChannelSwitchDelay(),
1826 "Expected TX to start at main PHY switch end");
1827 }
1828 });
1829
1830 events.emplace_back(WIFI_MAC_CTL_ACK);
1831
1832 events.emplace_back(
1834 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1835 if (testIndex == NON_HT_PPDU_DONT_USE_MAC_HDR)
1836 {
1837 Simulator::Schedule(MilliSeconds(2), [=, this]() {
1838 // check that trace infos have been received
1840 true,
1841 "Did not receive the expected trace infos");
1842 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
1843
1844 if (++m_testIndex < static_cast<uint8_t>(COUNT))
1845 {
1846 RunOne();
1847 }
1848 });
1849 }
1850 });
1851
1852 // In the NON_HT_PPDU_DONT_USE_MAC_HDR scenario, the main PHY does not switch back to the
1853 // preferred link after the transmission of the broadcast frame, so the QoS data frame with
1854 // TID 0 is transmitted (on link 1) before the QoS data frame with TID 4 (on link 0)
1855 const auto pos =
1856 (testIndex == NON_HT_PPDU_DONT_USE_MAC_HDR ? m_events.cend() : m_events.cbegin());
1857 m_events.splice(pos, events);
1858}
1859
1860void
1862{
1863 const auto testIndex = static_cast<TestScenario>(m_testIndex);
1864
1865 const auto bcastTxVector =
1866 m_apMac->GetWifiRemoteStationManager(m_linkIdForTid0)
1867 ->GetGroupcastTxVector(m_bcastFrame->GetHeader(),
1868 m_apMac->GetWifiPhy(m_linkIdForTid0)->GetChannelWidth());
1869 const auto bcastTxDuration =
1871 bcastTxVector,
1872 m_apMac->GetWifiPhy(m_linkIdForTid0)->GetPhyBand());
1873
1874 const auto mode = (testIndex >= NON_HT_PPDU_DONT_USE_MAC_HDR ? OfdmPhy::GetOfdmRate6Mbps()
1875 : HtPhy::GetHtMcs0());
1876
1877 m_switchMainPhyBackDelay = bcastTxDuration;
1878 if (testIndex != LONG_SWITCH_BACK_DELAY_DONT_USE_MAC_HDR &&
1880 {
1881 // make switch main PHY back delay at least two channel switch delays shorter than the
1882 // PPDU TX duration
1884 }
1885
1886 const auto interruptSwitch =
1887 (testIndex == RXSTART_WHILE_SWITCH_INTERRUPT || testIndex == NON_HT_PPDU_DONT_USE_MAC_HDR ||
1889 const auto useMacHeader =
1890 (testIndex == NON_HT_PPDU_USE_MAC_HDR || testIndex == LONG_SWITCH_BACK_DELAY_USE_MAC_HDR);
1891
1892 m_apMac->GetWifiRemoteStationManager(m_linkIdForTid0)
1893 ->SetAttribute("NonUnicastMode", WifiModeValue(mode));
1894 m_staMacs[0]->GetEmlsrManager()->SetAttribute("SwitchMainPhyBackDelay",
1896 m_staMacs[0]->GetEmlsrManager()->SetAttribute("InterruptSwitch", BooleanValue(interruptSwitch));
1897 m_staMacs[0]->GetEmlsrManager()->SetAttribute("UseNotifiedMacHdr", BooleanValue(useMacHeader));
1898 m_staMacs[0]->GetEmlsrManager()->SetAttribute("CheckAccessOnMainPhyLink", BooleanValue(false));
1899 // no in-device interference, just to avoid starting MSD timer causing RTS-CTS exchanges
1900 m_staMacs[0]->GetEmlsrManager()->SetAttribute("InDeviceInterference", BooleanValue(false));
1901
1902 NS_LOG_INFO("Starting test #" << +m_testIndex << "\n");
1903 m_dlPktDone = false;
1904
1905 // wait some more time to ensure that backoffs count down to zero and then generate a packet
1906 // at the EMLSR client. When notified of the main PHY switch, we decide when the AP MLD has to
1907 // transmit a broadcast frame
1908 Simulator::Schedule(MilliSeconds(5), [=, this]() {
1909 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 1, 500));
1910 });
1911
1912 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
1913 auto advEmlsrMgr = DynamicCast<AdvancedEmlsrManager>(m_staMacs[0]->GetEmlsrManager());
1914
1915 m_events.emplace_back(
1917 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1918 const auto phyHdrDuration = WifiPhy::CalculatePhyPreambleAndHeaderDuration(txVector);
1919 const auto txDuration =
1921 txVector,
1922 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
1923 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
1925 "Expected a broadcast frame");
1926 NS_TEST_EXPECT_MSG_EQ(+linkId,
1928 "Broadcast frame transmitted on wrong link");
1929 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
1930 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1931 "Unexpected TA for the broadcast frame");
1932 NS_TEST_EXPECT_MSG_EQ(txVector.GetMode(), mode, "Unexpected WifiMode");
1933
1934 switch (testIndex)
1935 {
1937 // main PHY is switching before the end of PHY header reception and
1938 // the switch main PHY back timer is running
1939 Simulator::Schedule(phyHdrDuration - TimeStep(1), [=, this]() {
1941 mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}),
1943 "Main PHY is not switching at the end of PHY header reception");
1944 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
1946 "Main PHY is switching to a wrong link");
1947 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
1948 true,
1949 "Main PHY switch back timer should be running");
1950 });
1951 // main PHY is still switching right after the end of PHY header reception, but
1952 // the switch main PHY back timer has been stopped
1953 Simulator::Schedule(phyHdrDuration + TimeStep(2), [=, this]() {
1955 mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}),
1957 "Main PHY is not switching at the end of PHY header reception");
1958 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
1960 "Main PHY is switching to a wrong link");
1961 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
1962 false,
1963 "Main PHY switch back timer should have been stopped");
1964 });
1965 // main PHY is expected to switch back when the ongoing switch terminates
1966 m_expectedMainPhySwitchBackTime = Simulator::Now() + mainPhy->GetDelayUntilIdle();
1967 break;
1968
1970 // main PHY is switching before the end of PHY header reception and
1971 // the switch main PHY back timer is running
1972 Simulator::Schedule(phyHdrDuration - TimeStep(1), [=, this]() {
1974 mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}),
1976 "Main PHY is not switching at the end of PHY header reception");
1977 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
1979 "Main PHY is switching to a wrong link");
1980 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
1981 true,
1982 "Main PHY switch back timer should be running");
1983 });
1984 // main PHY is switching back right after the end of PHY header reception, but
1985 // the switch main PHY back timer has been stopped
1986 Simulator::Schedule(phyHdrDuration + TimeStep(2), [=, this]() {
1988 mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}),
1990 "Main PHY is not switching at the end of PHY header reception");
1991 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
1992 +m_mainPhyId,
1993 "Main PHY is switching to a wrong link");
1994 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
1995 false,
1996 "Main PHY switch back timer should have been stopped");
1997 });
1998 // main PHY is expected to switch back when the reception of PHY header ends
1999 m_expectedMainPhySwitchBackTime = Simulator::Now() + phyHdrDuration + TimeStep(1);
2000 break;
2001
2003 // main PHY is switching back at the end of PHY header reception and
2004 // the switch main PHY back timer has been stopped
2005 Simulator::Schedule(phyHdrDuration, [=, this]() {
2007 mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}),
2009 "Main PHY is not switching at the end of PHY header reception");
2010 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
2011 +m_mainPhyId,
2012 "Main PHY is switching to a wrong link");
2013 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2014 false,
2015 "Main PHY switch back timer should have been stopped");
2016 });
2017 // main PHY is expected to switch back when the reception of PHY header ends
2019 Simulator::Now() + mainPhy->GetDelayUntilIdle() + TimeStep(1);
2020 break;
2021
2023 // when the main PHY completes the channel switch, it is not connected to the aux
2024 // PHY link and the switch main PHY back timer is running
2025 Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1), [=, this]() {
2026 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.disconnected,
2027 true,
2028 "Main PHY should be waiting to be connected to a link");
2029 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
2031 "Main PHY is waiting to be connected to a wrong link");
2032 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2033 true,
2034 "Main PHY switch back timer should be running");
2035 // when PIFS check is performed at the end of the main PHY switch, the medium
2036 // is found busy and a backoff value is generated; make sure that this value is
2037 // at most 2 to ensure the conditions expected by this scenario
2038 if (auto beTxop = m_staMacs[0]->GetQosTxop(AC_BE);
2039 beTxop->GetBackoffSlots(m_linkIdForTid0) > 2)
2040 {
2041 beTxop->StartBackoffNow(2, m_linkIdForTid0);
2042 m_staMacs[0]
2043 ->GetChannelAccessManager(m_linkIdForTid0)
2044 ->NotifyAckTimeoutResetNow(); // force restart access timeout
2045 }
2046 });
2047 // once the PPDU is received, the main PHY is connected to the aux PHY and the
2048 // switch main PHY back timer is still running
2049 Simulator::Schedule(txDuration + TimeStep(1), [=, this]() {
2051 mainPhy->GetState()->IsStateSwitching(),
2052 false,
2053 "Main PHY should not be switching at the end of PPDU reception");
2054 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.disconnected,
2055 false,
2056 "Main PHY should have been connected to a link");
2057 NS_TEST_ASSERT_MSG_EQ(m_staMacs[0]->GetLinkForPhy(m_mainPhyId).has_value(),
2058 true,
2059 "Main PHY should have been connected to a link");
2060 NS_TEST_EXPECT_MSG_EQ(+m_staMacs[0]->GetLinkForPhy(m_mainPhyId).value(),
2062 "Main PHY is connected to a wrong link");
2063 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2064 true,
2065 "Main PHY switch back timer should be running");
2066 });
2067 break;
2068
2071 // when the main PHY completes the channel switch, it is not connected to the aux
2072 // PHY link and the switch main PHY back timer is running. The aux PHY is in RX
2073 // state and has MAC header info available
2074 Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1), [=, this]() {
2075 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.disconnected,
2076 true,
2077 "Main PHY should be waiting to be connected to a link");
2078 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
2080 "Main PHY is waiting to be connected to a wrong link");
2081 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2082 true,
2083 "Main PHY switch back timer should be running");
2084 const auto auxPhy = m_staMacs[0]->GetDevice()->GetPhy(m_linkIdForTid0);
2085 NS_TEST_EXPECT_MSG_EQ(auxPhy->IsStateRx(),
2086 true,
2087 "Aux PHY should be in RX state");
2088 auto remTime = auxPhy->GetTimeToMacHdrEnd(SU_STA_ID);
2089 NS_TEST_ASSERT_MSG_EQ(remTime.has_value(),
2090 true,
2091 "No MAC header info available");
2092 if (testIndex == LONG_SWITCH_BACK_DELAY_USE_MAC_HDR)
2093 {
2094 // when PIFS check is performed at the end of the main PHY switch, the
2095 // medium is found busy and a backoff value is generated; make sure that
2096 // this value is at least 7 to ensure that the backoff timer is still
2097 // running when the switch main PHY back timer is expected to expire
2098 if (auto beTxop = m_staMacs[0]->GetQosTxop(AC_BE);
2099 beTxop->GetBackoffSlots(m_linkIdForTid0) <= 6)
2100 {
2101 beTxop->StartBackoffNow(7, m_linkIdForTid0);
2102 }
2103 }
2104 // main PHY is expected to switch back when the MAC header is received
2105 m_expectedMainPhySwitchBackTime = Simulator::Now() + remTime.value();
2106 // once the MAC header is received, the main PHY switches back and the
2107 // switch main PHY back timer is stopped
2108 Simulator::Schedule(remTime.value() + TimeStep(1), [=, this]() {
2110 mainPhy->GetState()->IsStateSwitching(),
2111 true,
2112 "Main PHY should be switching after receiving the MAC header");
2113 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
2114 +m_mainPhyId,
2115 "Main PHY should be switching to the preferred link");
2116 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2117 false,
2118 "Main PHY switch back timer should not be running");
2119 });
2120 });
2121 break;
2122
2124 // when the main PHY completes the channel switch, it is not connected to the aux
2125 // PHY link and the switch main PHY back timer is running
2126 Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1), [=, this]() {
2127 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.disconnected,
2128 true,
2129 "Main PHY should be waiting to be connected to a link");
2130 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
2132 "Main PHY is waiting to be connected to a wrong link");
2133 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2134 true,
2135 "Main PHY switch back timer should be running");
2136 // when PIFS check is performed at the end of the main PHY switch, the medium
2137 // is found busy and a backoff value is generated; make sure that this value is
2138 // at least 7 to ensure that the backoff timer is still running when the switch
2139 // main PHY back timer is expected to expire
2140 if (auto beTxop = m_staMacs[0]->GetQosTxop(AC_BE);
2141 beTxop->GetBackoffSlots(m_linkIdForTid0) <= 6)
2142 {
2143 beTxop->StartBackoffNow(7, m_linkIdForTid0);
2144 }
2145 });
2146 // once the PPDU is received, the switch main PHY back timer is stopped and the
2147 // main PHY switches back to the preferred link
2148 Simulator::Schedule(txDuration + TimeStep(2), [=, this]() {
2150 mainPhy->GetState()->IsStateSwitching(),
2151 true,
2152 "Main PHY should be switching at the end of PPDU reception");
2153 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
2154 +m_mainPhyId,
2155 "Main PHY should be switching back to preferred link");
2156 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2157 false,
2158 "Main PHY switch back timer should be not running");
2159 });
2160 // main PHY is expected to switch back when the reception of PPDU ends
2161 m_expectedMainPhySwitchBackTime = Simulator::Now() + txDuration + TimeStep(1);
2162 break;
2163
2164 default:
2165 NS_TEST_ASSERT_MSG_EQ(true, false, "Unexpected scenario: " << +m_testIndex);
2166 }
2167 });
2168
2169 m_events.emplace_back(
2171 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
2172 NS_TEST_EXPECT_MSG_EQ(+linkId,
2174 "Unicast frame transmitted on wrong link");
2175 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
2176 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
2177 "Unexpected RA for the unicast frame");
2178 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
2179 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
2180 "Unexpected TA for the unicast frame");
2181
2182 if (testIndex == NON_HT_PPDU_DONT_USE_MAC_HDR)
2183 {
2184 Simulator::Schedule(TimeStep(1), [=, this]() {
2185 // UL TXOP started, main PHY switch back time was cancelled
2186 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2187 false,
2188 "Main PHY switch back timer should not be running");
2189 });
2190 }
2191 });
2192
2193 m_events.emplace_back(
2195 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
2196 const auto phyHdrDuration = WifiPhy::CalculatePhyPreambleAndHeaderDuration(txVector);
2197 const auto txDuration =
2199 txVector,
2200 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2201
2202 if (testIndex == NON_HT_PPDU_DONT_USE_MAC_HDR)
2203 {
2204 // main PHY is expected to switch back when the UL TXOP ends
2206 }
2207 else
2208 {
2209 Simulator::Schedule(MilliSeconds(2), [=, this]() {
2210 // check that trace infos have been received
2212 true,
2213 "Did not receive the expected trace infos");
2214 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
2215
2216 if (++m_testIndex < static_cast<uint8_t>(COUNT))
2217 {
2218 RunOne();
2219 }
2220 });
2221 }
2222 });
2223}
2224
2226 : EmlsrOperationsTestBase("Verify operations during the NAV and CCA check in the last PIFS")
2227{
2228 m_mainPhyId = 1;
2229 m_linksToEnableEmlsrOn = {0, 1, 2};
2230 m_nEmlsrStations = 1;
2232
2235 m_establishBaDl = {};
2236 m_establishBaUl = {0};
2237 m_duration = Seconds(0.5);
2238}
2239
2240void
2242{
2243 Config::SetDefault("ns3::EmlsrManager::AuxPhyTxCapable", BooleanValue(false));
2244 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(false));
2245 Config::SetDefault("ns3::ChannelAccessManager::NSlotsLeft", UintegerValue(m_nSlotsLeft));
2246 // Use only one link for DL and UL traffic
2247 std::string mapping = "0 " + std::to_string(m_linkIdForTid0);
2248 Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingDl", StringValue(mapping));
2249 Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingUl", StringValue(mapping));
2250 Config::SetDefault("ns3::AdvancedEmlsrManager::UseAuxPhyCca", BooleanValue(true));
2251 Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth", UintegerValue(20));
2252
2253 // use 40 MHz channels
2254 m_channelsStr = {"{3, 40, BAND_2_4GHZ, 0}"s,
2255 "{38, 40, BAND_5GHZ, 0}"s,
2256 "{3, 40, BAND_6GHZ, 0}"s};
2257
2259}
2260
2261void
2263 uint8_t phyId,
2264 WifiConstPsduMap psduMap,
2265 WifiTxVector txVector,
2266 double txPowerW)
2267{
2268 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2269
2270 const auto psdu = psduMap.cbegin()->second;
2271 const auto& hdr = psdu->GetHeader(0);
2272
2273 // nothing to do before setup is completed
2274 if (!m_setupDone)
2275 {
2276 return;
2277 }
2278
2279 auto linkId = mac->GetLinkForPhy(phyId);
2280 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(),
2281 true,
2282 "PHY " << +phyId << " is not operating on any link");
2283
2284 if (!m_events.empty())
2285 {
2286 // check that the expected frame is being transmitted
2287 NS_TEST_EXPECT_MSG_EQ(m_events.front().hdrType,
2288 hdr.GetType(),
2289 "Unexpected MAC header type for frame #" << ++m_processedEvents);
2290 // perform actions/checks, if any
2291 if (m_events.front().func)
2292 {
2293 m_events.front().func(psdu, txVector, linkId.value());
2294 }
2295
2296 m_events.pop_front();
2297 }
2298}
2299
2300void
2302{
2303 Simulator::Schedule(MilliSeconds(5), [=, this]() {
2304 m_setupDone = true;
2305 RunOne();
2306 });
2307}
2308
2309void
2311{
2314
2315 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
2316
2318}
2319
2320void
2322{
2323 const auto useAuxPhyCca = ((m_testIndex & 0x1) == 1);
2324 const auto scenario = static_cast<TestScenario>(m_testIndex >> 1);
2325
2326 // aux PHY operating on the link on which TID 0 is mapped
2327 const auto auxPhy = m_staMacs[0]->GetDevice()->GetPhy(m_linkIdForTid0);
2328 const auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
2329 const auto slot = auxPhy->GetSlot();
2330 const auto pifs = auxPhy->GetSifs() + slot;
2331 const auto timeToBackoffEnd = slot * m_nSlotsLeft;
2332 NS_TEST_ASSERT_MSG_GT(timeToBackoffEnd, pifs + slot, "m_nSlotsLeft too small for this test");
2333
2334 const auto switchDelay =
2336 ? timeToBackoffEnd + slot
2337 : (scenario == LESS_THAN_PIFS_UNTIL_BACKOFF_END ? timeToBackoffEnd - pifs + slot
2338 : timeToBackoffEnd - pifs - slot));
2339
2340 m_staMacs[0]->GetEmlsrManager()->SetAttribute("UseAuxPhyCca", BooleanValue(useAuxPhyCca));
2341 mainPhy->SetAttribute("ChannelSwitchDelay", TimeValue(switchDelay));
2342
2343 NS_LOG_INFO("Starting test #" << m_testIndex << "\n");
2344
2345 // the AP sends a broadcast frame on the link on which TID 0 is mapped
2348 hdr.SetAddr2(m_apMac->GetFrameExchangeManager(m_linkIdForTid0)->GetAddress());
2349 hdr.SetAddr3(m_apMac->GetAddress());
2350 hdr.SetDsFrom();
2351 hdr.SetDsNotTo();
2352 hdr.SetQosTid(0);
2353
2354 m_apMac->GetQosTxop(AC_BE)->Queue(Create<WifiMpdu>(Create<Packet>(1000), hdr));
2355
2356 m_events.emplace_back(
2358 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
2359 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
2361 "Expected a broadcast frame");
2362 NS_TEST_EXPECT_MSG_EQ(+linkId,
2364 "Broadcast frame transmitted on wrong link");
2365 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
2366 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
2367 "Unexpected TA for the broadcast frame");
2368 const auto txDuration =
2370 txVector,
2371 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2372 const auto emlsrBeEdca = m_staMacs[0]->GetQosTxop(AC_BE);
2373
2374 // generate a packet at the EMLSR client while the medium on the link on which TID 0
2375 // is mapped is still busy, so that a backoff value is generated. The backoff counter
2376 // is configured to be equal to the m_nSlotsLeft value
2377 Simulator::Schedule(txDuration - TimeStep(1), [=, this]() {
2378 emlsrBeEdca->StartBackoffNow(m_nSlotsLeft, m_linkIdForTid0);
2379 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
2380 GetApplication(UPLINK, 0, 1, 500));
2381 });
2382
2383 // given that the backoff counter equals m_nSlotsLeft, we expect that, an AIFS after the
2384 // end of the broadcast frame transmission, the NSlotsLeftAlert trace is fired and the
2385 // main PHY starts switching to the link on which TID 0 is mapped
2386 const auto aifs = auxPhy->GetSifs() + emlsrBeEdca->GetAifsn(m_linkIdForTid0) * slot;
2387 Simulator::Schedule(txDuration + aifs + TimeStep(1), [=, this]() {
2388 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
2389 true,
2390 "Expected the main PHY to be switching");
2391 NS_TEST_EXPECT_MSG_EQ(mainPhy->GetDelayUntilIdle(),
2392 switchDelay - TimeStep(1),
2393 "Unexpected end of main PHY channel switch");
2394
2395 const auto now = Simulator::Now();
2396 switch (scenario)
2397 {
2399 if (!useAuxPhyCca)
2400 {
2401 m_expectedTxStart = now + mainPhy->GetDelayUntilIdle() + pifs;
2403 }
2404 else
2405 {
2406 m_expectedTxStart = now + mainPhy->GetDelayUntilIdle();
2408 }
2409 break;
2411 if (!useAuxPhyCca)
2412 {
2413 m_expectedTxStart = now + mainPhy->GetDelayUntilIdle() + pifs;
2415 }
2416 else
2417 {
2419 ->GetChannelAccessManager(m_linkIdForTid0)
2420 ->GetBackoffEndFor(emlsrBeEdca);
2422 }
2423 break;
2426 ->GetChannelAccessManager(m_linkIdForTid0)
2427 ->GetBackoffEndFor(emlsrBeEdca);
2429 break;
2430 default:
2431 NS_ABORT_MSG("Unexpected scenario " << +static_cast<uint8_t>(scenario));
2432 }
2433 });
2434 });
2435
2436 m_events.emplace_back(
2438 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
2439 NS_TEST_EXPECT_MSG_EQ(+linkId,
2441 "Unicast frame transmitted on wrong link");
2442 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
2443 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
2444 "Unexpected TA for the unicast frame");
2447 "Unexpected start TX time for unicast frame");
2449 txVector.GetChannelWidth(),
2450 "Unexpected channel width for the unicast frame");
2451 });
2452
2453 m_events.emplace_back(
2455 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
2456 Simulator::Schedule(MilliSeconds(2), [=, this]() {
2457 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
2458
2459 if (++m_testIndex < static_cast<uint8_t>(COUNT) * 2)
2460 {
2461 RunOne();
2462 }
2463 });
2464 });
2465}
2466
2468 : TestSuite("wifi-emlsr-link-switch", Type::UNIT)
2469{
2470 for (bool switchAuxPhy : {true, false})
2471 {
2472 for (bool resetCamStateAndInterruptSwitch : {true, false})
2473 {
2474 for (auto auxPhyMaxChWidth : {MHz_u{20}, MHz_u{40}, MHz_u{80}, MHz_u{160}})
2475 {
2477 {switchAuxPhy, resetCamStateAndInterruptSwitch, auxPhyMaxChWidth}),
2478 TestCase::Duration::QUICK);
2479 }
2480 }
2481 }
2482
2483 AddTestCase(new EmlsrCheckNavAndCcaLastPifsTest(), TestCase::Duration::QUICK);
2484 AddTestCase(new EmlsrIcfSentDuringMainPhySwitchTest(), TestCase::Duration::QUICK);
2485 AddTestCase(new EmlsrSwitchMainPhyBackTest(), TestCase::Duration::QUICK);
2486
2487 AddTestCase(new EmlsrCcaBusyTest(MHz_u{20}), TestCase::Duration::QUICK);
2488 AddTestCase(new EmlsrCcaBusyTest(MHz_u{80}), TestCase::Duration::QUICK);
2489
2490 for (const auto switchAuxPhy : {true, false})
2491 {
2492 for (const auto auxPhyTxCapable : {true, false})
2493 {
2494 AddTestCase(new SingleLinkEmlsrTest(switchAuxPhy, auxPhyTxCapable),
2495 TestCase::Duration::QUICK);
2496 }
2497 }
2498}
2499
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.
Check NAV and CCA in the last PIFS test.
const uint8_t m_nSlotsLeft
value for the CAM NSlotsLeft attribute
void DoRun() override
Implementation to actually run this TestCase.
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
const uint8_t m_linkIdForTid0
ID of the link on which TID 0 is mapped.
std::size_t m_testIndex
index to iterate over test scenarios
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.
TestScenario
Enumeration indicating the tested scenario.
bool m_setupDone
whether association, BA, ... have been done
void RunOne()
Runs a test case and invokes itself for the next test case.
Time m_expectedTxStart
expected start time for frame transmission
std::list< Events > m_events
list of events for a test run
MHz_u m_expectedWidth
expected channel width for frame transmission
std::size_t m_processedEvents
number of processed events
const MHz_u m_auxPhyWidth
aux PHY channel width
void DoSetup() override
Implementation to do any local setup required for this TestCase.
const MHz_u m_mainPhyWidth
main PHY channel width
Check ICF reception while main PHY is switching.
ChannelSwitchEnd m_csdIndex
index to iterate over channel switch durations
void CheckInDeviceInterference(const std::string &testStr, uint8_t linkId, Time duration)
Check that the in-device interference generated by a transmission of the given duration on the given ...
const uint8_t m_linkIdForTid3
ID of the link on which TID 3 is mapped.
void RunOne()
Runs a test case and invokes itself for the next test case.
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
bool m_setupDone
whether association, BA, ... have been done
std::array< WifiSpectrumBandInfo, 3 > m_bands
bands of the 3 frequency channels
void DoRun() override
Implementation to actually run this TestCase.
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_testIndex
index to iterate over test scenarios
void GenerateNoiseOnAllLinks(Ptr< WifiMac > mac, Time duration)
Generate noise on all the links of the given MAC for the given time duration.
void EmlsrLinkSwitchCb(uint8_t linkId, Ptr< WifiPhy > phy, bool connected)
Callback connected to the EmlsrLinkSwitch trace source of the StaWifiMac of the EMLSR client.
std::size_t m_processedEvents
number of processed events
std::optional< MainPhySwitchInfo > m_switchFrom
info for main PHY leaving a link
std::list< Events > m_events
list of events for a test run
ChannelSwitchEnd
Enumeration indicating the duration of a main PHY channel switch compared to the ICF fields.
void DoSetup() override
Implementation to do any local setup required for this TestCase.
std::optional< MainPhySwitchInfo > m_switchTo
info for main PHY connected to a link
std::string m_testStr
test scenario description
Base class for EMLSR Operations tests.
virtual 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
std::vector< uint8_t > m_establishBaDl
the TIDs for which BA needs to be established 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< 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.
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
Ptr< PacketSocketClient > GetApplication(TrafficDirection dir, std::size_t staId, std::size_t count, std::size_t pktSize, uint8_t priority=0) const
std::vector< FrameInfo > m_txPsdus
transmitted PSDUs
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.
std::vector< Time > m_transitionDelay
Transition Delay advertised by the non-AP MLD.
std::array< std::string, 3 > m_channelsStr
array of strings defining the channels for the MLD links
std::vector< Ptr< StaWifiMac > > m_staMacs
MACs of the non-AP MLDs.
std::size_t m_nPhysPerEmlsrDevice
number of PHYs per EMLSR client
std::vector< uint8_t > m_establishBaUl
the TIDs for which BA needs to be established with the AP as recipient
Switch main PHY back timer test.
void DoRun() override
Implementation to actually run this TestCase.
const uint8_t m_linkIdForTid4
ID of the link on which TID 4 is mapped.
Ptr< WifiMpdu > m_bcastFrame
the broadcast frame sent by the AP MLD
void InsertEventsForQosTid4()
Insert events corresponding to the UL TXOP to transmit the QoS Data frame with TID 4.
std::size_t m_processedEvents
number of processed events
void MainPhySwitchInfoCallback(std::size_t index, const EmlsrMainPhySwitchTrace &info) override
Callback connected to the EMLSR Manager MainPhySwitch trace source.
bool m_dlPktDone
whether the DL packet has been generated
bool m_setupDone
whether association, BA, ... have been done
void DoSetup() override
Implementation to do any local setup required for this TestCase.
std::list< Events > m_events
list of events for a test run
Time m_expectedMainPhySwitchBackTime
expected main PHY switch back time
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_testIndex
index to iterate over test scenarios
void RunOne()
Runs a test case and invokes itself for the next test case.
TestScenario
Enumeration indicating the tested scenario.
Time m_switchMainPhyBackDelay
the switch main PHY back delay
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
const uint8_t m_linkIdForTid0
ID of the link on which TID 0 is mapped.
AttributeValue implementation for Boolean.
Definition boolean.h:26
static constexpr bool DONT_REQUEST_ACCESS
do not request channel access when PHY switch ends
static WifiMode GetHtMcs0()
Return MCS 0 from HT MCS values.
handles interference calculations
Time GetEnergyDuration(Watt_u energy, const WifiSpectrumBandInfo &band)
void SetList(const std::list< uint64_t > &packetlist)
static Mac48Address GetBroadcast()
static WifiMode GetOfdmRate6Mbps()
Return a WifiMode for OFDM at 6 Mbps.
AttributeValue implementation for Pointer.
Ptr< T > Get() const
Definition pointer.h:223
Smart pointer class similar to boost::intrusive_ptr.
Template class for packet Queues.
Definition queue.h:257
static EventId Schedule(const Time &delay, FUNC f, Ts &&... args)
Schedule an event to expire after delay.
Definition simulator.h:561
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:595
static void Stop()
Tell the Simulator the calling event should be the last one executed.
Definition simulator.cc:175
Hold variables of type string.
Definition string.h:45
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
@ US
microsecond
Definition nstime.h:107
@ NS
nanosecond
Definition nstime.h:108
AttributeValue implementation for Time.
Definition nstime.h:1432
Hold an unsigned integer type.
Definition uinteger.h:34
static std::pair< CategoryValue, ActionValue > Peek(Ptr< const Packet > pkt)
Peek an Action header from the given packet.
Implements the IEEE 802.11 MAC header.
void SetAddr1(Mac48Address address)
Fill the Address 1 field with the given address.
void SetQosTid(uint8_t tid)
Set the TID for the QoS header.
void SetDsFrom()
Set the From DS bit in the Frame Control field.
void SetAddr2(Mac48Address address)
Fill the Address 2 field with the given address.
void SetAddr3(Mac48Address address)
Fill the Address 3 field with the given address.
void SetDsNotTo()
Un-set the To DS bit in the Frame Control field.
AttributeValue implementation for WifiMode.
Definition wifi-mode.h:244
std::tuple< uint8_t, MHz_u, WifiPhyBand, uint8_t > ChannelTuple
Tuple identifying a segment of an operating channel.
Definition wifi-phy.h:937
static Time CalculateTxDuration(uint32_t size, const WifiTxVector &txVector, WifiPhyBand band, uint16_t staId=SU_STA_ID)
Definition wifi-phy.cc:1563
static Time CalculatePhyPreambleAndHeaderDuration(const WifiTxVector &txVector)
Definition wifi-phy.cc:1556
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.
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:886
#define NS_ABORT_MSG(msg)
Unconditional abnormal program termination with a message.
Definition abort.h:38
#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 > 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_LT(actual, limit, msg)
Test that an actual value is less than a limit and report and abort if not.
Definition test.h:699
#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:1369
Time NanoSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1381
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition nstime.h:1345
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1357
@ 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.
const Time MEDIUM_SYNC_THRESHOLD
The aMediumSyncThreshold defined by Sec. 35.3.16.18.1 of 802.11be D4.0.
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
Ptr< T1 > DynamicCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:580
@ WIFI_MAC_CTL_TRIGGER
@ WIFI_MAC_CTL_RTS
@ WIFI_MAC_CTL_CTS
@ WIFI_MAC_MGT_ACTION
@ WIFI_MAC_MGT_ASSOCIATION_RESPONSE
@ WIFI_MAC_CTL_ACK
@ WIFI_MAC_MGT_ASSOCIATION_REQUEST
@ WIFI_MAC_CTL_BACKRESP
@ WIFI_MAC_CTL_END
@ WIFI_MAC_QOSDATA
Watt_u DbmToW(dBm_u val)
Convert from dBm to Watts.
Definition wifi-utils.cc:32
std::unordered_map< uint16_t, Ptr< const WifiPsdu > > WifiConstPsduMap
Map of const PSDUs indexed by STA-ID.
Definition wifi-ppdu.h:38
static constexpr uint16_t SU_STA_ID
STA_ID to identify a single user (SU)
STL namespace.
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::string_view GetName() const =0
Struct to trace that main PHY switched to leave a link on which an aux PHY was expected to gain a TXO...