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
1681 m_apMac->GetWifiPhy(m_linkIdForTid4)->GetSlot() + TimeStep(1),
1682 [=, this]() {
1683 auto advEmlsrMgr =
1684 DynamicCast<AdvancedEmlsrManager>(m_staMacs[0]->GetEmlsrManager());
1685
1686 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.disconnected,
1687 true,
1688 "Expected the main PHY to be switching");
1690 +advEmlsrMgr->m_mainPhySwitchInfo.to,
1691 +(advEmlsrMgr->m_interruptSwitching ? m_linkIdForTid4 : m_mainPhyId),
1692 "Test index " << +m_testIndex << ": Main PHY is switching to wrong link");
1693 });
1694 });
1696 };
1697
1699 info.GetName() == "TxopNotGainedOnAuxPhyLink")
1700 {
1701 NS_LOG_INFO("Main PHY switches back\n");
1702
1703 const auto& traceInfo = static_cast<const EmlsrSwitchMainPhyBackTrace&>(info);
1704
1705 switch (static_cast<TestScenario>(m_testIndex))
1706 {
1709 NS_TEST_EXPECT_MSG_EQ((traceInfo.elapsed.IsStrictlyPositive() &&
1710 traceInfo.elapsed < m_switchMainPhyBackDelay),
1711 true,
1712 "Unexpected value for the elapsed field");
1713 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.has_value(),
1714 true,
1715 "earlySwitchReason should hold a value");
1716 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.value(),
1717 WifiExpectedAccessReason::RX_END,
1718 "Unexpected earlySwitchReason value");
1719 NS_TEST_EXPECT_MSG_EQ(traceInfo.isSwitching, true, "Unexpected value for isSwitching");
1720 break;
1721
1723 NS_TEST_EXPECT_MSG_EQ((traceInfo.elapsed.IsStrictlyPositive() &&
1724 traceInfo.elapsed < m_switchMainPhyBackDelay),
1725 true,
1726 "Unexpected value for the elapsed field");
1727 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.has_value(),
1728 true,
1729 "earlySwitchReason should hold a value");
1730 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.value(),
1731 WifiExpectedAccessReason::BUSY_END,
1732 "Unexpected earlySwitchReason value");
1733 NS_TEST_EXPECT_MSG_EQ(traceInfo.isSwitching, false, "Unexpected value for isSwitching");
1734 break;
1735
1737 NS_TEST_EXPECT_MSG_EQ((traceInfo.elapsed.IsStrictlyPositive() &&
1738 traceInfo.elapsed < m_switchMainPhyBackDelay),
1739 true,
1740 "Unexpected value for the elapsed field");
1741 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.has_value(),
1742 true,
1743 "earlySwitchReason should hold a value");
1744 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.value(),
1745 WifiExpectedAccessReason::RX_END,
1746 "Unexpected earlySwitchReason value");
1747 NS_TEST_EXPECT_MSG_EQ(traceInfo.isSwitching, false, "Unexpected value for isSwitching");
1748 break;
1749
1751 NS_TEST_EXPECT_MSG_EQ((traceInfo.elapsed.IsStrictlyPositive() &&
1752 traceInfo.elapsed >= m_switchMainPhyBackDelay),
1753 true,
1754 "Unexpected value for the elapsed field");
1755 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.has_value(),
1756 true,
1757 "earlySwitchReason should hold a value");
1758 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.value(),
1759 WifiExpectedAccessReason::BACKOFF_END,
1760 "Unexpected earlySwitchReason value");
1761 NS_TEST_EXPECT_MSG_EQ(traceInfo.isSwitching, false, "Unexpected value for isSwitching");
1762 break;
1763
1765 NS_TEST_EXPECT_MSG_EQ((traceInfo.elapsed.IsStrictlyPositive() &&
1766 traceInfo.elapsed < m_switchMainPhyBackDelay),
1767 true,
1768 "Unexpected value for the elapsed field");
1769 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.has_value(),
1770 true,
1771 "earlySwitchReason should hold a value");
1772 NS_TEST_EXPECT_MSG_EQ(traceInfo.earlySwitchReason.value(),
1773 WifiExpectedAccessReason::BACKOFF_END,
1774 "Unexpected earlySwitchReason value");
1775 NS_TEST_EXPECT_MSG_EQ(traceInfo.isSwitching, false, "Unexpected value for isSwitching");
1776 break;
1777
1778 default:
1779 NS_TEST_ASSERT_MSG_EQ(true, false, "Unexpected scenario: " << +m_testIndex);
1780 }
1781
1782 genTid4Frame();
1783 }
1784
1785 if (m_expectedMainPhySwitchBackTime == Simulator::Now() && info.GetName() == "TxopEnded")
1786 {
1787 NS_LOG_INFO("Main PHY switches back\n");
1788
1790 +static_cast<uint8_t>(NON_HT_PPDU_DONT_USE_MAC_HDR),
1791 "Unexpected TxopEnded reason for switching main PHY back");
1792
1793 genTid4Frame();
1794 }
1795}
1796
1797void
1799{
1800 const auto testIndex = static_cast<TestScenario>(m_testIndex);
1801 std::list<Events> events;
1802
1803 events.emplace_back(
1805 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1806 NS_TEST_EXPECT_MSG_EQ(+linkId,
1808 "Unicast frame with TID 4 transmitted on wrong link");
1809 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
1810 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1811 "Unexpected RA for the unicast frame with TID 4");
1812 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
1813 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
1814 "Unexpected TA for the unicast frame with TID 4");
1815 NS_TEST_EXPECT_MSG_EQ(+(*psdu->GetTids().cbegin()),
1816 4,
1817 "Expected a unicast frame with TID 4");
1818 // if switching can be interrupted, the frame with TID 4 is transmitted as soon as
1819 // the main PHY completes the switching to link 0
1820 if (auto advEmlsrMgr =
1821 DynamicCast<AdvancedEmlsrManager>(m_staMacs[0]->GetEmlsrManager());
1822 advEmlsrMgr->m_interruptSwitching)
1823 {
1824 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
1825 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.start +
1826 mainPhy->GetChannelSwitchDelay(),
1828 "Expected TX to start at main PHY switch end");
1829 }
1830 });
1831
1832 events.emplace_back(WIFI_MAC_CTL_ACK);
1833
1834 events.emplace_back(
1836 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1837 if (testIndex == NON_HT_PPDU_DONT_USE_MAC_HDR)
1838 {
1839 Simulator::Schedule(MilliSeconds(2), [=, this]() {
1840 // check that trace infos have been received
1842 true,
1843 "Did not receive the expected trace infos");
1844 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
1845
1846 if (++m_testIndex < static_cast<uint8_t>(COUNT))
1847 {
1848 RunOne();
1849 }
1850 });
1851 }
1852 });
1853
1854 // In the NON_HT_PPDU_DONT_USE_MAC_HDR scenario, the main PHY does not switch back to the
1855 // preferred link after the transmission of the broadcast frame, so the QoS data frame with
1856 // TID 0 is transmitted (on link 1) before the QoS data frame with TID 4 (on link 0)
1857 const auto pos =
1858 (testIndex == NON_HT_PPDU_DONT_USE_MAC_HDR ? m_events.cend() : m_events.cbegin());
1859 m_events.splice(pos, events);
1860}
1861
1862void
1864{
1865 const auto testIndex = static_cast<TestScenario>(m_testIndex);
1866
1867 const auto bcastTxVector =
1868 m_apMac->GetWifiRemoteStationManager(m_linkIdForTid0)
1869 ->GetGroupcastTxVector(m_bcastFrame->GetHeader(),
1870 m_apMac->GetWifiPhy(m_linkIdForTid0)->GetChannelWidth());
1871 const auto bcastTxDuration =
1873 bcastTxVector,
1874 m_apMac->GetWifiPhy(m_linkIdForTid0)->GetPhyBand());
1875
1876 const auto mode = (testIndex >= NON_HT_PPDU_DONT_USE_MAC_HDR ? OfdmPhy::GetOfdmRate6Mbps()
1877 : HtPhy::GetHtMcs0());
1878
1879 m_switchMainPhyBackDelay = bcastTxDuration;
1880 if (testIndex != LONG_SWITCH_BACK_DELAY_DONT_USE_MAC_HDR &&
1882 {
1883 // make switch main PHY back delay at least two channel switch delays shorter than the
1884 // PPDU TX duration
1886 }
1887
1888 const auto interruptSwitch =
1889 (testIndex == RXSTART_WHILE_SWITCH_INTERRUPT || testIndex == NON_HT_PPDU_DONT_USE_MAC_HDR ||
1891 const auto useMacHeader =
1892 (testIndex == NON_HT_PPDU_USE_MAC_HDR || testIndex == LONG_SWITCH_BACK_DELAY_USE_MAC_HDR);
1893
1894 m_apMac->GetWifiRemoteStationManager(m_linkIdForTid0)
1895 ->SetAttribute("NonUnicastMode", WifiModeValue(mode));
1896 m_staMacs[0]->GetEmlsrManager()->SetAttribute("SwitchMainPhyBackDelay",
1898 m_staMacs[0]->GetEmlsrManager()->SetAttribute("InterruptSwitch", BooleanValue(interruptSwitch));
1899 m_staMacs[0]->GetEmlsrManager()->SetAttribute("UseNotifiedMacHdr", BooleanValue(useMacHeader));
1900 m_staMacs[0]->GetEmlsrManager()->SetAttribute("CheckAccessOnMainPhyLink", BooleanValue(false));
1901 // no in-device interference, just to avoid starting MSD timer causing RTS-CTS exchanges
1902 m_staMacs[0]->GetEmlsrManager()->SetAttribute("InDeviceInterference", BooleanValue(false));
1903
1904 NS_LOG_INFO("Starting test #" << +m_testIndex << "\n");
1905 m_dlPktDone = false;
1906
1907 // wait some more time to ensure that backoffs count down to zero and then generate a packet
1908 // at the EMLSR client. When notified of the main PHY switch, we decide when the AP MLD has to
1909 // transmit a broadcast frame
1910 Simulator::Schedule(MilliSeconds(5), [=, this]() {
1911 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 1, 500));
1912 });
1913
1914 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
1915 auto advEmlsrMgr = DynamicCast<AdvancedEmlsrManager>(m_staMacs[0]->GetEmlsrManager());
1916
1917 m_events.emplace_back(
1919 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1920 const auto phyHdrDuration = WifiPhy::CalculatePhyPreambleAndHeaderDuration(txVector);
1921 const auto txDuration =
1923 txVector,
1924 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
1925 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
1927 "Expected a broadcast frame");
1928 NS_TEST_EXPECT_MSG_EQ(+linkId,
1930 "Broadcast frame transmitted on wrong link");
1931 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
1932 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
1933 "Unexpected TA for the broadcast frame");
1934 NS_TEST_EXPECT_MSG_EQ(txVector.GetMode(), mode, "Unexpected WifiMode");
1935
1936 switch (testIndex)
1937 {
1939 // main PHY is switching before the end of PHY header reception and
1940 // the switch main PHY back timer is running
1941 Simulator::Schedule(phyHdrDuration - TimeStep(1), [=, this]() {
1943 mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}),
1945 "Main PHY is not switching at the end of PHY header reception");
1946 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
1948 "Main PHY is switching to a wrong link");
1949 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
1950 true,
1951 "Main PHY switch back timer should be running");
1952 });
1953 // main PHY is still switching right after the end of PHY header reception, but
1954 // the switch main PHY back timer has been stopped
1955 Simulator::Schedule(phyHdrDuration + TimeStep(2), [=, this]() {
1957 mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}),
1959 "Main PHY is not switching at the end of PHY header reception");
1960 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
1962 "Main PHY is switching to a wrong link");
1963 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
1964 false,
1965 "Main PHY switch back timer should have been stopped");
1966 });
1967 // main PHY is expected to switch back when the ongoing switch terminates
1968 m_expectedMainPhySwitchBackTime = Simulator::Now() + mainPhy->GetDelayUntilIdle();
1969 break;
1970
1972 // main PHY is switching before the end of PHY header reception and
1973 // the switch main PHY back timer is running
1974 Simulator::Schedule(phyHdrDuration - TimeStep(1), [=, this]() {
1976 mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}),
1978 "Main PHY is not switching at the end of PHY header reception");
1979 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
1981 "Main PHY is switching to a wrong link");
1982 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
1983 true,
1984 "Main PHY switch back timer should be running");
1985 });
1986 // main PHY is switching back right after the end of PHY header reception, but
1987 // the switch main PHY back timer has been stopped
1988 Simulator::Schedule(phyHdrDuration + TimeStep(2), [=, this]() {
1990 mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}),
1992 "Main PHY is not switching at the end of PHY header reception");
1993 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
1994 +m_mainPhyId,
1995 "Main PHY is switching to a wrong link");
1996 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
1997 false,
1998 "Main PHY switch back timer should have been stopped");
1999 });
2000 // main PHY is expected to switch back when the reception of PHY header ends
2001 m_expectedMainPhySwitchBackTime = Simulator::Now() + phyHdrDuration + TimeStep(1);
2002 break;
2003
2005 // main PHY is switching back at the end of PHY header reception and
2006 // the switch main PHY back timer has been stopped
2007 Simulator::Schedule(phyHdrDuration, [=, this]() {
2009 mainPhy->GetState()->GetLastTime({WifiPhyState::SWITCHING}),
2011 "Main PHY is not switching at the end of PHY header reception");
2012 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
2013 +m_mainPhyId,
2014 "Main PHY is switching to a wrong link");
2015 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2016 false,
2017 "Main PHY switch back timer should have been stopped");
2018 });
2019 // main PHY is expected to switch back when the reception of PHY header ends
2021 Simulator::Now() + mainPhy->GetDelayUntilIdle() + TimeStep(1);
2022 break;
2023
2025 // when the main PHY completes the channel switch, it is not connected to the aux
2026 // PHY link and the switch main PHY back timer is running
2027 Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1), [=, this]() {
2028 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.disconnected,
2029 true,
2030 "Main PHY should be waiting to be connected to a link");
2031 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
2033 "Main PHY is waiting to be connected to a wrong link");
2034 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2035 true,
2036 "Main PHY switch back timer should be running");
2037 // when PIFS check is performed at the end of the main PHY switch, the medium
2038 // is found busy and a backoff value is generated; make sure that this value is
2039 // at most 2 to ensure the conditions expected by this scenario
2040 if (auto beTxop = m_staMacs[0]->GetQosTxop(AC_BE);
2041 beTxop->GetBackoffSlots(m_linkIdForTid0) > 2)
2042 {
2043 beTxop->StartBackoffNow(2, m_linkIdForTid0);
2044 m_staMacs[0]
2045 ->GetChannelAccessManager(m_linkIdForTid0)
2046 ->NotifyAckTimeoutResetNow(); // force restart access timeout
2047 }
2048 });
2049 // once the PPDU is received, the main PHY is connected to the aux PHY and the
2050 // switch main PHY back timer is still running
2051 Simulator::Schedule(txDuration + TimeStep(1), [=, this]() {
2053 mainPhy->GetState()->IsStateSwitching(),
2054 false,
2055 "Main PHY should not be switching at the end of PPDU reception");
2056 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.disconnected,
2057 false,
2058 "Main PHY should have been connected to a link");
2059 NS_TEST_ASSERT_MSG_EQ(m_staMacs[0]->GetLinkForPhy(m_mainPhyId).has_value(),
2060 true,
2061 "Main PHY should have been connected to a link");
2062 NS_TEST_EXPECT_MSG_EQ(+m_staMacs[0]->GetLinkForPhy(m_mainPhyId).value(),
2064 "Main PHY is connected to a wrong link");
2065 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2066 true,
2067 "Main PHY switch back timer should be running");
2068 });
2069 break;
2070
2073 // when the main PHY completes the channel switch, it is not connected to the aux
2074 // PHY link and the switch main PHY back timer is running. The aux PHY is in RX
2075 // state and has MAC header info available
2076 Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1), [=, this]() {
2077 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.disconnected,
2078 true,
2079 "Main PHY should be waiting to be connected to a link");
2080 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
2082 "Main PHY is waiting to be connected to a wrong link");
2083 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2084 true,
2085 "Main PHY switch back timer should be running");
2086 const auto auxPhy = m_staMacs[0]->GetDevice()->GetPhy(m_linkIdForTid0);
2087 NS_TEST_EXPECT_MSG_EQ(auxPhy->IsStateRx(),
2088 true,
2089 "Aux PHY should be in RX state");
2090 auto remTime = auxPhy->GetTimeToMacHdrEnd(SU_STA_ID);
2091 NS_TEST_ASSERT_MSG_EQ(remTime.has_value(),
2092 true,
2093 "No MAC header info available");
2094 if (testIndex == LONG_SWITCH_BACK_DELAY_USE_MAC_HDR)
2095 {
2096 // when PIFS check is performed at the end of the main PHY switch, the
2097 // medium is found busy and a backoff value is generated; make sure that
2098 // this value is at least 7 to ensure that the backoff timer is still
2099 // running when the switch main PHY back timer is expected to expire
2100 if (auto beTxop = m_staMacs[0]->GetQosTxop(AC_BE);
2101 beTxop->GetBackoffSlots(m_linkIdForTid0) <= 6)
2102 {
2103 beTxop->StartBackoffNow(7, m_linkIdForTid0);
2104 }
2105 }
2106 // main PHY is expected to switch back when the MAC header is received
2107 m_expectedMainPhySwitchBackTime = Simulator::Now() + remTime.value();
2108 // once the MAC header is received, the main PHY switches back and the
2109 // switch main PHY back timer is stopped
2110 Simulator::Schedule(remTime.value() + TimeStep(1), [=, this]() {
2112 mainPhy->GetState()->IsStateSwitching(),
2113 true,
2114 "Main PHY should be switching after receiving the MAC header");
2115 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
2116 +m_mainPhyId,
2117 "Main PHY should be switching to the preferred link");
2118 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2119 false,
2120 "Main PHY switch back timer should not be running");
2121 });
2122 });
2123 break;
2124
2126 // when the main PHY completes the channel switch, it is not connected to the aux
2127 // PHY link and the switch main PHY back timer is running
2128 Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1), [=, this]() {
2129 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_mainPhySwitchInfo.disconnected,
2130 true,
2131 "Main PHY should be waiting to be connected to a link");
2132 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
2134 "Main PHY is waiting to be connected to a wrong link");
2135 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2136 true,
2137 "Main PHY switch back timer should be running");
2138 // when PIFS check is performed at the end of the main PHY switch, the medium
2139 // is found busy and a backoff value is generated; make sure that this value is
2140 // at least 7 to ensure that the backoff timer is still running when the switch
2141 // main PHY back timer is expected to expire
2142 if (auto beTxop = m_staMacs[0]->GetQosTxop(AC_BE);
2143 beTxop->GetBackoffSlots(m_linkIdForTid0) <= 6)
2144 {
2145 beTxop->StartBackoffNow(7, m_linkIdForTid0);
2146 }
2147 });
2148 // once the PPDU is received, the switch main PHY back timer is stopped and the
2149 // main PHY switches back to the preferred link
2150 Simulator::Schedule(txDuration + TimeStep(2), [=, this]() {
2152 mainPhy->GetState()->IsStateSwitching(),
2153 true,
2154 "Main PHY should be switching at the end of PPDU reception");
2155 NS_TEST_EXPECT_MSG_EQ(+advEmlsrMgr->m_mainPhySwitchInfo.to,
2156 +m_mainPhyId,
2157 "Main PHY should be switching back to preferred link");
2158 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2159 false,
2160 "Main PHY switch back timer should be not running");
2161 });
2162 // main PHY is expected to switch back when the reception of PPDU ends
2163 m_expectedMainPhySwitchBackTime = Simulator::Now() + txDuration + TimeStep(1);
2164 break;
2165
2166 default:
2167 NS_TEST_ASSERT_MSG_EQ(true, false, "Unexpected scenario: " << +m_testIndex);
2168 }
2169 });
2170
2171 m_events.emplace_back(
2173 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
2174 NS_TEST_EXPECT_MSG_EQ(+linkId,
2176 "Unicast frame transmitted on wrong link");
2177 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
2178 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
2179 "Unexpected RA for the unicast frame");
2180 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
2181 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
2182 "Unexpected TA for the unicast frame");
2183
2184 if (testIndex == NON_HT_PPDU_DONT_USE_MAC_HDR)
2185 {
2186 Simulator::Schedule(TimeStep(1), [=, this]() {
2187 // UL TXOP started, main PHY switch back time was cancelled
2188 NS_TEST_EXPECT_MSG_EQ(advEmlsrMgr->m_switchMainPhyBackEvent.IsPending(),
2189 false,
2190 "Main PHY switch back timer should not be running");
2191 });
2192 }
2193 });
2194
2195 m_events.emplace_back(
2197 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
2198 const auto txDuration =
2200 txVector,
2201 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2202
2203 if (testIndex == NON_HT_PPDU_DONT_USE_MAC_HDR)
2204 {
2205 // main PHY is expected to switch back when the UL TXOP ends
2207 }
2208 else
2209 {
2210 Simulator::Schedule(MilliSeconds(2), [=, this]() {
2211 // check that trace infos have been received
2213 true,
2214 "Did not receive the expected trace infos");
2215 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
2216
2217 if (++m_testIndex < static_cast<uint8_t>(COUNT))
2218 {
2219 RunOne();
2220 }
2221 });
2222 }
2223 });
2224}
2225
2227 : EmlsrOperationsTestBase("Verify operations during the NAV and CCA check in the last PIFS")
2228{
2229 m_mainPhyId = 1;
2230 m_linksToEnableEmlsrOn = {0, 1, 2};
2231 m_nEmlsrStations = 1;
2233
2236 m_establishBaDl = {};
2237 m_establishBaUl = {0};
2238 m_duration = Seconds(0.5);
2239}
2240
2241void
2243{
2244 Config::SetDefault("ns3::EmlsrManager::AuxPhyTxCapable", BooleanValue(false));
2245 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(false));
2246 Config::SetDefault("ns3::ChannelAccessManager::NSlotsLeft", UintegerValue(m_nSlotsLeft));
2247 // Use only one link for DL and UL traffic
2248 std::string mapping = "0 " + std::to_string(m_linkIdForTid0);
2249 Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingDl", StringValue(mapping));
2250 Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingUl", StringValue(mapping));
2251 Config::SetDefault("ns3::AdvancedEmlsrManager::UseAuxPhyCca", BooleanValue(true));
2252 Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth", UintegerValue(20));
2253
2254 // use 40 MHz channels
2255 m_channelsStr = {"{3, 40, BAND_2_4GHZ, 0}"s,
2256 "{38, 40, BAND_5GHZ, 0}"s,
2257 "{3, 40, BAND_6GHZ, 0}"s};
2258
2260}
2261
2262void
2264 uint8_t phyId,
2265 WifiConstPsduMap psduMap,
2266 WifiTxVector txVector,
2267 double txPowerW)
2268{
2269 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2270
2271 const auto psdu = psduMap.cbegin()->second;
2272 const auto& hdr = psdu->GetHeader(0);
2273
2274 // nothing to do before setup is completed
2275 if (!m_setupDone)
2276 {
2277 return;
2278 }
2279
2280 auto linkId = mac->GetLinkForPhy(phyId);
2281 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(),
2282 true,
2283 "PHY " << +phyId << " is not operating on any link");
2284
2285 if (!m_events.empty())
2286 {
2287 // check that the expected frame is being transmitted
2288 NS_TEST_EXPECT_MSG_EQ(m_events.front().hdrType,
2289 hdr.GetType(),
2290 "Unexpected MAC header type for frame #" << ++m_processedEvents);
2291 // perform actions/checks, if any
2292 if (m_events.front().func)
2293 {
2294 m_events.front().func(psdu, txVector, linkId.value());
2295 }
2296
2297 m_events.pop_front();
2298 }
2299}
2300
2301void
2303{
2304 Simulator::Schedule(MilliSeconds(5), [=, this]() {
2305 m_setupDone = true;
2306 RunOne();
2307 });
2308}
2309
2310void
2312{
2315
2316 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
2317
2319}
2320
2321void
2323{
2324 const auto useAuxPhyCca = ((m_testIndex & 0x1) == 1);
2325 const auto scenario = static_cast<TestScenario>(m_testIndex >> 1);
2326
2327 // aux PHY operating on the link on which TID 0 is mapped
2328 const auto auxPhy = m_staMacs[0]->GetDevice()->GetPhy(m_linkIdForTid0);
2329 const auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
2330 const auto slot = auxPhy->GetSlot();
2331 const auto pifs = auxPhy->GetSifs() + slot;
2332 const auto timeToBackoffEnd = slot * m_nSlotsLeft;
2333 NS_TEST_ASSERT_MSG_GT(timeToBackoffEnd, pifs + slot, "m_nSlotsLeft too small for this test");
2334
2335 const auto switchDelay =
2337 ? timeToBackoffEnd + slot
2338 : (scenario == LESS_THAN_PIFS_UNTIL_BACKOFF_END ? timeToBackoffEnd - pifs + slot
2339 : timeToBackoffEnd - pifs - slot));
2340
2341 m_staMacs[0]->GetEmlsrManager()->SetAttribute("UseAuxPhyCca", BooleanValue(useAuxPhyCca));
2342 mainPhy->SetAttribute("ChannelSwitchDelay", TimeValue(switchDelay));
2343
2344 NS_LOG_INFO("Starting test #" << m_testIndex << "\n");
2345
2346 // the AP sends a broadcast frame on the link on which TID 0 is mapped
2349 hdr.SetAddr2(m_apMac->GetFrameExchangeManager(m_linkIdForTid0)->GetAddress());
2350 hdr.SetAddr3(m_apMac->GetAddress());
2351 hdr.SetDsFrom();
2352 hdr.SetDsNotTo();
2353 hdr.SetQosTid(0);
2354
2355 m_apMac->GetQosTxop(AC_BE)->Queue(Create<WifiMpdu>(Create<Packet>(1000), hdr));
2356
2357 m_events.emplace_back(
2359 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
2360 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
2362 "Expected a broadcast frame");
2363 NS_TEST_EXPECT_MSG_EQ(+linkId,
2365 "Broadcast frame transmitted on wrong link");
2366 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
2367 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
2368 "Unexpected TA for the broadcast frame");
2369 const auto txDuration =
2371 txVector,
2372 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2373 const auto emlsrBeEdca = m_staMacs[0]->GetQosTxop(AC_BE);
2374
2375 // generate a packet at the EMLSR client while the medium on the link on which TID 0
2376 // is mapped is still busy, so that a backoff value is generated. The backoff counter
2377 // is configured to be equal to the m_nSlotsLeft value
2378 Simulator::Schedule(txDuration - TimeStep(1), [=, this]() {
2379 emlsrBeEdca->StartBackoffNow(m_nSlotsLeft, m_linkIdForTid0);
2380 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
2381 GetApplication(UPLINK, 0, 1, 500));
2382 });
2383
2384 // given that the backoff counter equals m_nSlotsLeft, we expect that, an AIFS after the
2385 // end of the broadcast frame transmission, the NSlotsLeftAlert trace is fired and the
2386 // main PHY starts switching to the link on which TID 0 is mapped
2387 const auto aifs = auxPhy->GetSifs() + emlsrBeEdca->GetAifsn(m_linkIdForTid0) * slot;
2388 Simulator::Schedule(txDuration + aifs + TimeStep(1), [=, this]() {
2389 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
2390 true,
2391 "Expected the main PHY to be switching");
2392 NS_TEST_EXPECT_MSG_EQ(mainPhy->GetDelayUntilIdle(),
2393 switchDelay - TimeStep(1),
2394 "Unexpected end of main PHY channel switch");
2395
2396 const auto now = Simulator::Now();
2397 switch (scenario)
2398 {
2400 if (!useAuxPhyCca)
2401 {
2402 m_expectedTxStart = now + mainPhy->GetDelayUntilIdle() + pifs;
2404 }
2405 else
2406 {
2407 m_expectedTxStart = now + mainPhy->GetDelayUntilIdle();
2409 }
2410 break;
2412 if (!useAuxPhyCca)
2413 {
2414 m_expectedTxStart = now + mainPhy->GetDelayUntilIdle() + pifs;
2416 }
2417 else
2418 {
2420 ->GetChannelAccessManager(m_linkIdForTid0)
2421 ->GetBackoffEndFor(emlsrBeEdca);
2423 }
2424 break;
2427 ->GetChannelAccessManager(m_linkIdForTid0)
2428 ->GetBackoffEndFor(emlsrBeEdca);
2430 break;
2431 default:
2432 NS_ABORT_MSG("Unexpected scenario " << +static_cast<uint8_t>(scenario));
2433 }
2434 });
2435 });
2436
2437 m_events.emplace_back(
2439 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
2440 NS_TEST_EXPECT_MSG_EQ(+linkId,
2442 "Unicast frame transmitted on wrong link");
2443 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
2444 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
2445 "Unexpected TA for the unicast frame");
2448 "Unexpected start TX time for unicast frame");
2450 txVector.GetChannelWidth(),
2451 "Unexpected channel width for the unicast frame");
2452 });
2453
2454 m_events.emplace_back(
2456 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
2457 Simulator::Schedule(MilliSeconds(2), [=, this]() {
2458 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
2459
2460 if (++m_testIndex < static_cast<uint8_t>(COUNT) * 2)
2461 {
2462 RunOne();
2463 }
2464 });
2465 });
2466}
2467
2469 : TestSuite("wifi-emlsr-link-switch", Type::UNIT)
2470{
2471 for (bool switchAuxPhy : {true, false})
2472 {
2473 for (bool resetCamStateAndInterruptSwitch : {true, false})
2474 {
2475 for (auto auxPhyMaxChWidth : {MHz_u{20}, MHz_u{40}, MHz_u{80}, MHz_u{160}})
2476 {
2478 {switchAuxPhy, resetCamStateAndInterruptSwitch, auxPhyMaxChWidth}),
2479 TestCase::Duration::QUICK);
2480 }
2481 }
2482 }
2483
2484 AddTestCase(new EmlsrCheckNavAndCcaLastPifsTest(), TestCase::Duration::QUICK);
2485 AddTestCase(new EmlsrIcfSentDuringMainPhySwitchTest(), TestCase::Duration::QUICK);
2486 AddTestCase(new EmlsrSwitchMainPhyBackTest(), TestCase::Duration::QUICK);
2487
2488 AddTestCase(new EmlsrCcaBusyTest(MHz_u{20}), TestCase::Duration::QUICK);
2489 AddTestCase(new EmlsrCcaBusyTest(MHz_u{80}), TestCase::Duration::QUICK);
2490
2491 for (const auto switchAuxPhy : {true, false})
2492 {
2493 for (const auto auxPhyTxCapable : {true, false})
2494 {
2495 AddTestCase(new SingleLinkEmlsrTest(switchAuxPhy, auxPhyTxCapable),
2496 TestCase::Duration::QUICK);
2497 }
2498 }
2499}
2500
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:96
@ US
microsecond
Definition nstime.h:109
@ NS
nanosecond
Definition nstime.h:110
AttributeValue implementation for Time.
Definition nstime.h:1456
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:439
#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:1393
Time NanoSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1405
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition nstime.h:1369
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1381
@ 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:585
@ 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...