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 =
528 (totalSwitchDelay >
530 const auto msdIsRunning = msdWasRunning || startMsd;
532 m_staMacs[0],
534 msdIsRunning,
535 std::string("because total switch delay was ") +
536 std::to_string(totalSwitchDelay.GetNanoSeconds()) + "ns");
537 });
538 });
539 });
540 });
541 }
542 // block transmissions on all other links at non-AP MLD side
543 std::set<uint8_t> links{0, 1, 2};
544 links.erase(linkId);
545 m_staMacs[0]->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
546 AC_BE,
548 m_apMac->GetAddress(),
549 m_staMacs[0]->GetAddress(),
550 {0},
551 links);
552}
553
554void
556{
557 NS_TEST_ASSERT_MSG_NE(m_txPsdusPos, 0, "BA agreement establishment not completed");
558
559 // Expected frame exchanges after ML setup and EML OMN exchange:
560 // 1. (DL) ICF + CTS + ADDBA_REQ + ACK
561 // 2. (UL) ADDBA_RESP + ACK
562 // 3. (DL) ICF + CTS + DATA + BA
563 // 4. (DL) ICF + CTS + DATA + BA
564 // 5. (DL) ICF + CTS + DATA + BA
565 // 6. (DL) ICF + CTS + DATA + BA
566 // 7. (UL) ADDBA_REQ + ACK
567 // 8. (DL) ICF + CTS + ADDBA_RESP + ACK
568 // 9. (UL) DATA + BA
569 // 10. (UL) RTS - CTS timeout
570 // 11. (UL) (RTS + CTS + ) DATA + BA
571
572 // frame exchange 11 is protected if SwitchAuxPhy is false or (SwitchAuxPhy is true and) the
573 // main PHY switch can be interrupted
574 bool fe11protected = !m_switchAuxPhy || m_resetCamStateAndInterruptSwitch;
575
576 NS_TEST_EXPECT_MSG_EQ(m_countIcfFrames, 6, "Unexpected number of ICFs sent");
577
578 // frame exchanges without RTS because the EMLSR client sent the initial frame through main PHY
579 const std::size_t nFrameExchNoRts = fe11protected ? 3 : 4;
580
581 const std::size_t nFrameExchWithRts = fe11protected ? 1 : 0;
582
585 m_countIcfFrames * 4 + /* frames in frame exchange with ICF */
586 nFrameExchNoRts * 2 + /* frames in frame exchange without RTS */
587 nFrameExchWithRts * 4 + /* frames in frame exchange with RTS */
588 1, /* corrupted RTS */
589 "Insufficient number of TX PSDUs");
590
591 // m_txPsdusPos points to the first ICF
592 auto psduIt = std::next(m_txPsdus.cbegin(), m_txPsdusPos);
593
594 // lambda to increase psduIt while skipping Beacon frames
595 auto nextPsdu = [&]() {
596 do
597 {
598 ++psduIt;
599 } while (psduIt != m_txPsdus.cend() &&
600 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsBeacon());
601 };
602
603 const std::size_t nFrameExchanges =
604 m_countIcfFrames + nFrameExchNoRts + nFrameExchWithRts + 1 /* corrupted RTS */;
605
606 for (std::size_t i = 1; i <= nFrameExchanges; ++i)
607 {
608 if (i == 1 || (i >= 3 && i <= 6) || i == 8 || i == 10 || (i == 11 && fe11protected))
609 {
610 // frame exchanges with protection
611 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
612 (i < 9 ? psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsTrigger()
613 : psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsRts())),
614 true,
615 "Expected a Trigger Frame (ICF)");
616 nextPsdu();
617 if (i == 10)
618 {
619 continue; // corrupted RTS
620 }
621 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
622 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsCts()),
623 true,
624 "Expected a CTS");
625 nextPsdu();
626 }
627
628 if (i == 1 || i == 2 || i == 7 || i == 8) // frame exchanges with ADDBA REQ/RESP frames
629 {
630 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
631 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsMgt()),
632 true,
633 "Expected a management frame");
634 nextPsdu();
635 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
636 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsAck()),
637 true,
638 "Expected a Normal Ack");
639 }
640 else
641 {
642 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
643 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsQosData()),
644 true,
645 "Expected a QoS Data frame");
646 nextPsdu();
647 NS_TEST_EXPECT_MSG_EQ((psduIt->psduMap.size() == 1 &&
648 psduIt->psduMap.at(SU_STA_ID)->GetHeader(0).IsBlockAck()),
649 true,
650 "Expected a BlockAck");
651 }
652 nextPsdu();
653 }
654}
655
657 : EmlsrOperationsTestBase(std::string("Check EMLSR link switching (auxPhyMaxChWidth=") +
658 std::to_string(auxPhyMaxChWidth) + "MHz )"),
659 m_auxPhyMaxChWidth(auxPhyMaxChWidth),
660 m_channelSwitchDelay(MicroSeconds(75)),
661 m_currMainPhyLinkId(0),
662 m_nextMainPhyLinkId(0)
663{
666 m_linksToEnableEmlsrOn = {0, 1, 2}; // enable EMLSR on all links right after association
667 m_mainPhyId = 1;
668 m_establishBaUl = {0};
669 m_duration = Seconds(1.0);
671}
672
673void
681
682void
684{
685 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(true));
686 Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth", UintegerValue(m_auxPhyMaxChWidth));
687 Config::SetDefault("ns3::EmlsrManager::AuxPhyMaxModClass", StringValue("EHT"));
688 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(m_channelSwitchDelay));
689
691
692 // use channels of different widths
693 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0], m_staMacs[1]})
694 {
695 mac->GetWifiPhy(0)->SetOperatingChannel(
697 mac->GetWifiPhy(1)->SetOperatingChannel(
699 mac->GetWifiPhy(2)->SetOperatingChannel(
701 }
702}
703
704void
706{
707 m_staMacs[1]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 1, 1, 2000));
708
709 // force the transmission of the packet to happen now on the given link.
710 // Multiple ScheduleNow calls are needed because Node::AddApplication() schedules a call to
711 // Application::Initialize(), which schedules a call to Application::StartApplication(), which
712 // schedules a call to PacketSocketClient::Send(), which finally generates the packet
713 Simulator::ScheduleNow([=, this]() {
714 Simulator::ScheduleNow([=, this]() {
715 Simulator::ScheduleNow([=, this]() {
716 m_staMacs[1]->GetFrameExchangeManager(linkId)->StartTransmission(
717 m_staMacs[1]->GetQosTxop(AC_BE),
718 m_staMacs[1]->GetWifiPhy(linkId)->GetChannelWidth());
719 });
720 });
721 });
722
723 // check that the other MLD started transmitting on the correct link
724 Simulator::Schedule(TimeStep(1), [=, this]() {
725 NS_TEST_EXPECT_MSG_EQ(m_staMacs[1]->GetWifiPhy(linkId)->IsStateTx(),
726 true,
727 "At time " << Simulator::Now().As(Time::NS)
728 << ", other MLD did not start transmitting on link "
729 << +linkId);
730 });
731}
732
733void
735{
736 auto currMainPhyLinkId = m_staMacs[0]->GetLinkForPhy(m_mainPhyId);
737 NS_TEST_ASSERT_MSG_EQ(currMainPhyLinkId.has_value(),
738 true,
739 "Main PHY is not operating on any link");
740 m_currMainPhyLinkId = *currMainPhyLinkId;
742
743 // request the main PHY to switch to another link
744 m_staMacs[0]->GetEmlsrManager()->SwitchMainPhy(
746 false,
748 EmlsrDlTxopIcfReceivedByAuxPhyTrace{}); // trace info not used
749
750 // the other MLD transmits a packet to the AP
752
753 // schedule another packet transmission slightly (10 us) before the end of aux PHY switch
756 this,
758
759 // first checkpoint is after that the preamble of the PPDU has been received
761}
762
763/**
764 * ┌───────────────┐
765 * [link X] │ other to AP │CP3
766 * ──────────────────────────────┴───────────────┴──────────────────────────────────────────────
767 * |------ main PHY ------| |------------------- aux PHY ---------------------
768 * .\_ _/
769 * . \_ _/
770 * . \_ _/
771 * . \_ _/
772 * [link Y] . CP1 \/ CP2
773 * .┌───────────────┐
774 * .│ other to AP │
775 * ─────────────────────────┴───────────────┴────────────────────────────────────────────────────
776 * |------------ aux PHY ----------|---------------------- main PHY ----------------------------
777 *
778 */
779
780void
782{
783 // first checkpoint is after that the preamble of the first PPDU has been received
784 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
785
786 // 1. Main PHY is switching
787 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(), true, "Main PHY is not switching");
788
789 auto auxPhy = m_staMacs[0]->GetWifiPhy(m_nextMainPhyLinkId);
790 NS_TEST_EXPECT_MSG_NE(mainPhy, auxPhy, "Main PHY is operating on an unexpected link");
791
792 // 2. Aux PHY is receiving the PHY header
793 NS_TEST_EXPECT_MSG_EQ(auxPhy->GetInfoIfRxingPhyHeader().has_value(),
794 true,
795 "Aux PHY is not receiving a PHY header");
796
797 // 3. Main PHY dropped the preamble because it is switching
798 NS_TEST_EXPECT_MSG_EQ(mainPhy->GetInfoIfRxingPhyHeader().has_value(),
799 false,
800 "Main PHY is receiving a PHY header");
801
802 // 4. Channel access manager on destination link (Y) has been notified of CCA busy, but not
803 // until the end of transmission (main PHY dropped the preamble and notified CCA busy until
804 // end of transmission but the channel access manager on link Y does not yet have a listener
805 // attached to the main PHY; aux PHY notified CCA busy until the end of the PHY header field
806 // being received)
807 const auto caManager = m_staMacs[0]->GetChannelAccessManager(m_nextMainPhyLinkId);
808 const auto endTxTime = m_staMacs[1]->GetChannelAccessManager(m_nextMainPhyLinkId)->m_lastTxEnd;
809 NS_TEST_ASSERT_MSG_EQ(caManager->m_lastBusyEnd.contains(WIFI_CHANLIST_PRIMARY),
810 true,
811 "No CCA information for primary20 channel");
813 caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
815 "ChannelAccessManager on destination link not notified of CCA busy");
817 caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
818 endTxTime,
819 "ChannelAccessManager on destination link notified of CCA busy until end of transmission");
820
821 // second checkpoint is after that the main PHY completed the link switch
822 Simulator::Schedule(mainPhy->GetDelayUntilIdle() + TimeStep(1),
824 this);
825}
826
827void
829{
830 // second checkpoint is after that the main PHY completed the link switch. The channel access
831 // manager on destination link (Y) is expected to be notified by the main PHY that medium is
832 // busy until the end of the ongoing transmission
833 const auto caManager = m_staMacs[0]->GetChannelAccessManager(m_nextMainPhyLinkId);
834 const auto endTxTime = m_staMacs[1]->GetChannelAccessManager(m_nextMainPhyLinkId)->m_lastTxEnd;
835 NS_TEST_ASSERT_MSG_EQ(caManager->m_lastBusyEnd.contains(WIFI_CHANLIST_PRIMARY),
836 true,
837 "No CCA information for primary20 channel");
839 caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
841 "ChannelAccessManager on destination link not notified of CCA busy");
842 NS_TEST_EXPECT_MSG_GT_OR_EQ(caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
843 endTxTime,
844 "ChannelAccessManager on destination link not notified of CCA busy "
845 "until end of transmission");
846
847 // third checkpoint is after that the aux PHY completed the link switch
849}
850
851void
853{
854 // third checkpoint is after that the aux PHY completed the link switch. The channel access
855 // manager on source link (X) is expected to be notified by the aux PHY that medium is
856 // busy until the end of the ongoing transmission (even if the aux PHY was not listening to
857 // link X when transmission started, its interface on link X recorded the transmission)
858 const auto caManager = m_staMacs[0]->GetChannelAccessManager(m_currMainPhyLinkId);
859 const auto endTxTime = m_staMacs[1]->GetChannelAccessManager(m_currMainPhyLinkId)->m_lastTxEnd;
860 NS_TEST_ASSERT_MSG_EQ(caManager->m_lastBusyEnd.contains(WIFI_CHANLIST_PRIMARY),
861 true,
862 "No CCA information for primary20 channel");
863 NS_TEST_EXPECT_MSG_GT_OR_EQ(caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
865 "ChannelAccessManager on source link not notified of CCA busy");
866 NS_TEST_EXPECT_MSG_GT_OR_EQ(caManager->m_lastBusyEnd[WIFI_CHANLIST_PRIMARY],
867 endTxTime,
868 "ChannelAccessManager on source link not notified of CCA busy "
869 "until end of transmission");
870}
871
872SingleLinkEmlsrTest::SingleLinkEmlsrTest(bool switchAuxPhy, bool auxPhyTxCapable)
874 "Check EMLSR single link operation (switchAuxPhy=" + std::to_string(switchAuxPhy) +
875 ", auxPhyTxCapable=" + std::to_string(auxPhyTxCapable) + ")"),
876 m_switchAuxPhy(switchAuxPhy),
877 m_auxPhyTxCapable(auxPhyTxCapable)
878{
879 m_mainPhyId = 0;
884
885 // channel switch delay will be also set to 64 us
888 m_establishBaDl = {0};
889 m_establishBaUl = {0};
890 m_duration = Seconds(0.5);
891}
892
893void
895{
896 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(64)));
897 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(m_switchAuxPhy));
898 Config::SetDefault("ns3::EmlsrManager::AuxPhyTxCapable", BooleanValue(m_auxPhyTxCapable));
899
901}
902
903void
905 uint8_t phyId,
906 WifiConstPsduMap psduMap,
907 WifiTxVector txVector,
908 double txPowerW)
909{
910 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
911
912 const auto psdu = psduMap.cbegin()->second;
913 const auto& hdr = psdu->GetHeader(0);
914
915 // nothing to do in case of Beacon and CF-End frames
916 if (hdr.IsBeacon() || hdr.IsCfEnd())
917 {
918 return;
919 }
920
921 auto linkId = mac->GetLinkForPhy(phyId);
922 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(),
923 true,
924 "PHY " << +phyId << " is not operating on any link");
925 NS_TEST_EXPECT_MSG_EQ(+linkId.value(), 0, "TX occurred on unexpected link " << +linkId.value());
926
927 if (m_eventIt != m_events.cend())
928 {
929 // check that the expected frame is being transmitted
931 hdr.GetType(),
932 "Unexpected MAC header type for frame #"
933 << std::distance(m_events.cbegin(), m_eventIt));
934 // perform actions/checks, if any
935 if (m_eventIt->func)
936 {
937 m_eventIt->func(psdu, txVector);
938 }
939
940 ++m_eventIt;
941 }
942}
943
944void
946{
947 // lambda to check that AP MLD started the transition delay timer after the TX/RX of given frame
948 auto checkTransDelay = [this](Ptr<const WifiPsdu> psdu,
949 const WifiTxVector& txVector,
950 bool testUnblockedForOtherReasons,
951 const std::string& frameStr) {
952 const auto txDuration = WifiPhy::CalculateTxDuration(psdu->GetSize(),
953 txVector,
954 m_apMac->GetWifiPhy(0)->GetPhyBand());
955 Simulator::Schedule(txDuration + MicroSeconds(1), /* to account for propagation delay */
957 this,
958 m_apMac,
959 m_staMacs[0]->GetAddress(),
960 0,
961 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
962 true,
963 "Checking that AP MLD blocked transmissions to single link EMLSR "
964 "client after " +
965 frameStr,
966 testUnblockedForOtherReasons);
967 };
968
969 // expected sequence of transmitted frames
971 m_events.emplace_back(WIFI_MAC_CTL_ACK);
973 m_events.emplace_back(WIFI_MAC_CTL_ACK);
974
975 // EML OMN sent by EMLSR client
976 m_events.emplace_back(WIFI_MAC_MGT_ACTION,
977 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
978 // check that the address of the EMLSR client is seen as an MLD
979 // address
981 m_apMac->GetWifiRemoteStationManager(0)
982 ->GetMldAddress(m_staMacs[0]->GetAddress())
983 .has_value(),
984 true,
985 "Expected the EMLSR client address to be seen as an MLD address");
986 });
987 m_events.emplace_back(WIFI_MAC_CTL_ACK);
988 // EML OMN sent by AP MLD, protected by ICF
989 m_events.emplace_back(WIFI_MAC_CTL_TRIGGER);
990 m_events.emplace_back(WIFI_MAC_CTL_CTS);
991 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
992 m_events.emplace_back(WIFI_MAC_CTL_ACK,
993 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
994 // check that EMLSR mode has been enabled on link 0 of EMLSR client
996 m_staMacs[0]->IsEmlsrLink(0),
997 true,
998 "Expected EMLSR mode to be enabled on link 0 of EMLSR client");
999 });
1000
1001 // Establishment of BA agreement for downlink direction
1002
1003 // ADDBA REQUEST sent by AP MLD (protected by ICF)
1004 m_events.emplace_back(WIFI_MAC_CTL_TRIGGER);
1005 m_events.emplace_back(WIFI_MAC_CTL_CTS);
1006 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
1007 m_events.emplace_back(WIFI_MAC_CTL_ACK,
1008 [=](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
1009 // check that transition delay is started after reception of Ack
1010 checkTransDelay(psdu, txVector, false, "DL ADDBA REQUEST");
1011 });
1012
1013 // ADDBA RESPONSE sent by EMLSR client (no RTS because it is sent by main PHY)
1014 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
1015 m_events.emplace_back(WIFI_MAC_CTL_ACK,
1016 [=](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
1017 // check that transition delay is started after reception of Ack
1018 checkTransDelay(psdu, txVector, true, "DL ADDBA RESPONSE");
1019 });
1020
1021 // Downlink QoS data frame that triggered BA agreement establishment
1022 m_events.emplace_back(WIFI_MAC_CTL_TRIGGER);
1023 m_events.emplace_back(WIFI_MAC_CTL_CTS);
1024 m_events.emplace_back(WIFI_MAC_QOSDATA);
1025 m_events.emplace_back(WIFI_MAC_CTL_BACKRESP,
1026 [=](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
1027 // check that transition delay is started after reception of BlockAck
1028 checkTransDelay(psdu, txVector, true, "DL QoS Data");
1029 });
1030
1031 // Establishment of BA agreement for uplink direction
1032
1033 // ADDBA REQUEST sent by EMLSR client (no RTS because it is sent by main PHY)
1034 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
1035 m_events.emplace_back(WIFI_MAC_CTL_ACK,
1036 [=](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
1037 // check that transition delay is started after reception of Ack
1038 checkTransDelay(psdu, txVector, false, "UL ADDBA REQUEST");
1039 });
1040 // ADDBA RESPONSE sent by AP MLD (protected by ICF)
1041 m_events.emplace_back(WIFI_MAC_CTL_TRIGGER);
1042 m_events.emplace_back(WIFI_MAC_CTL_CTS);
1043 m_events.emplace_back(WIFI_MAC_MGT_ACTION);
1044 m_events.emplace_back(WIFI_MAC_CTL_ACK,
1045 [=](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
1046 // check that transition delay is started after reception of Ack
1047 checkTransDelay(psdu, txVector, true, "UL ADDBA RESPONSE");
1048 });
1049
1050 // Uplink QoS data frame that triggered BA agreement establishment
1051 m_events.emplace_back(WIFI_MAC_QOSDATA);
1052 m_events.emplace_back(WIFI_MAC_CTL_BACKRESP,
1053 [=](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) {
1054 // check that transition delay is started after reception of BlockAck
1055 checkTransDelay(psdu, txVector, true, "UL QoS Data");
1056 });
1057
1058 m_eventIt = m_events.cbegin();
1059
1062
1063 NS_TEST_EXPECT_MSG_EQ((m_eventIt == m_events.cend()), true, "Not all events took place");
1064
1066}
1067
1069 : EmlsrOperationsTestBase("Check ICF reception while main PHY is switching")
1070{
1071 m_mainPhyId = 0;
1072 m_linksToEnableEmlsrOn = {0, 1, 2};
1073 m_nEmlsrStations = 1;
1075
1076 // channel switch delay will be also set to 64 us
1079 m_establishBaDl = {0, 3};
1080 m_establishBaUl = {0, 3};
1081 m_duration = Seconds(0.5);
1082}
1083
1084void
1086{
1087 // channel switch delay will be modified during test scenarios
1088 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(64)));
1089 Config::SetDefault("ns3::WifiPhy::NotifyMacHdrRxEnd", BooleanValue(true));
1090 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(false));
1091 Config::SetDefault("ns3::EmlsrManager::AuxPhyTxCapable", BooleanValue(false));
1092 // AP MLD transmits both TID 0 and TID 3 on link 1
1093 Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingDl", StringValue("0,3 1"));
1094 // EMLSR client transmits TID 0 on link 1 and TID 3 on link 2
1095 Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingUl",
1096 StringValue("0 1; 3 " + std::to_string(m_linkIdForTid3)));
1097
1099
1100 m_staMacs[0]->TraceConnectWithoutContext(
1101 "EmlsrLinkSwitch",
1103
1104 for (uint8_t i = 0; i < m_staMacs[0]->GetDevice()->GetNPhys(); ++i)
1105 {
1106 m_bands.at(i) = m_staMacs[0]->GetDevice()->GetPhy(i)->GetBand(MHz_u{20}, 0);
1107 }
1108}
1109
1110void
1112{
1113 for (const auto linkId : mac->GetLinkIds())
1114 {
1115 auto phy = DynamicCast<SpectrumWifiPhy>(mac->GetWifiPhy(linkId));
1116 NS_TEST_ASSERT_MSG_NE(phy, nullptr, "No PHY on link " << +linkId);
1117 const auto txPower = phy->GetPower(1) + phy->GetTxGain();
1118
1119 auto psd = Create<SpectrumValue>(phy->GetCurrentInterface()->GetRxSpectrumModel());
1120 *psd = txPower;
1121
1122 auto spectrumSignalParams = Create<SpectrumSignalParameters>();
1123 spectrumSignalParams->duration = duration;
1124 spectrumSignalParams->txPhy = phy->GetCurrentInterface();
1125 spectrumSignalParams->txAntenna = phy->GetAntenna();
1126 spectrumSignalParams->psd = psd;
1127
1128 phy->StartRx(spectrumSignalParams, phy->GetCurrentInterface());
1129 }
1130}
1131
1132void
1134 uint8_t linkId,
1135 Time duration)
1136{
1137 for (const auto& phy : m_staMacs[0]->GetDevice()->GetPhys())
1138 {
1139 // ignore the PHY that is transmitting
1140 if (m_staMacs[0]->GetLinkForPhy(phy) == linkId)
1141 {
1142 continue;
1143 }
1144
1145 PointerValue ptr;
1146 phy->GetAttribute("InterferenceHelper", ptr);
1147 auto interferenceHelper = ptr.Get<InterferenceHelper>();
1148
1149 // we need to check that all the PHY interfaces recorded the in-device interference,
1150 // hence we consider a 20 MHz sub-band of the frequency channels of all the links
1151 for (uint8_t i = 0; i < m_staMacs[0]->GetNLinks(); ++i)
1152 {
1153 auto energyDuration =
1154 interferenceHelper->GetEnergyDuration(DbmToW(phy->GetCcaEdThreshold()),
1155 m_bands.at(i));
1156
1158 energyDuration,
1159 duration,
1160 m_testStr << ", " << frameTypeStr << ": Unexpected energy duration for PHY "
1161 << +phy->GetPhyId() << " in the band corresponding to link " << +i);
1162 }
1163 }
1164}
1165
1166void
1168 uint8_t phyId,
1169 WifiConstPsduMap psduMap,
1170 WifiTxVector txVector,
1171 double txPowerW)
1172{
1173 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
1174
1175 const auto psdu = psduMap.cbegin()->second;
1176 const auto& hdr = psdu->GetHeader(0);
1177
1178 // nothing to do before setup is completed
1179 if (!m_setupDone)
1180 {
1181 return;
1182 }
1183
1184 auto linkId = mac->GetLinkForPhy(phyId);
1185 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(),
1186 true,
1187 "PHY " << +phyId << " is not operating on any link");
1188
1189 if (!m_events.empty())
1190 {
1191 // check that the expected frame is being transmitted
1192 NS_TEST_EXPECT_MSG_EQ(m_events.front().hdrType,
1193 hdr.GetType(),
1194 "Unexpected MAC header type for frame #" << ++m_processedEvents);
1195 // perform actions/checks, if any
1196 if (m_events.front().func)
1197 {
1198 m_events.front().func(psdu, txVector, linkId.value());
1199 }
1200
1201 m_events.pop_front();
1202 }
1203}
1204
1205void
1211
1212void
1214{
1217
1218 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
1219
1221}
1222
1223void
1225 Ptr<WifiPhy> phy,
1226 bool connected)
1227{
1228 if (!m_setupDone)
1229 {
1230 return;
1231 }
1232
1233 if (!connected)
1234 {
1235 const auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
1236 NS_LOG_DEBUG("Main PHY leaving link " << +linkId << ", switch delay "
1237 << mainPhy->GetChannelSwitchDelay().As(Time::US)
1238 << "\n");
1240 m_switchTo.reset();
1241 }
1242 else
1243 {
1244 NS_LOG_DEBUG((phy->GetPhyId() == m_mainPhyId ? "Main" : "Aux")
1245 << " PHY connected to link " << +linkId << "\n");
1246 if (phy->GetPhyId() == m_mainPhyId)
1247 {
1249 m_switchFrom.reset();
1250 }
1251 }
1252}
1253
1254void
1256{
1257 const auto useMacHdrInfo = ((m_testIndex & 0b001) != 0);
1258 const auto interruptSwitch = ((m_testIndex & 0b010) != 0);
1259 const auto switchToOtherLink = ((m_testIndex & 0b100) != 0);
1260
1261 const auto keepMainPhyAfterDlTxop = useMacHdrInfo;
1262
1263 m_staMacs[0]->GetEmlsrManager()->SetAttribute("UseNotifiedMacHdr", BooleanValue(useMacHdrInfo));
1264 auto advEmlsrMgr = DynamicCast<AdvancedEmlsrManager>(m_staMacs[0]->GetEmlsrManager());
1265 NS_TEST_ASSERT_MSG_NE(advEmlsrMgr, nullptr, "Advanced EMLSR Manager required");
1266 advEmlsrMgr->SetAttribute("InterruptSwitch", BooleanValue(interruptSwitch));
1267 advEmlsrMgr->SetAttribute("KeepMainPhyAfterDlTxop", BooleanValue(keepMainPhyAfterDlTxop));
1268
1269 m_testStr = "SwitchToOtherLink=" + std::to_string(switchToOtherLink) +
1270 ", InterruptSwitch=" + std::to_string(interruptSwitch) +
1271 ", UseMacHdrInfo=" + std::to_string(useMacHdrInfo) +
1272 ", KeepMainPhyAfterDlTxop=" + std::to_string(keepMainPhyAfterDlTxop) +
1273 ", ChannelSwitchDurationIdx=" + std::to_string(m_csdIndex);
1274 NS_LOG_INFO("Starting test: " << m_testStr << "\n");
1275
1276 // generate noise on all the links of the AP MLD and the EMLSR client, so as to align the EDCA
1277 // backoff boundaries
1278 Simulator::Schedule(MilliSeconds(3), [=, this]() {
1281 });
1282
1283 // wait some more time to ensure that backoffs count down to zero and then generate a packet
1284 // at the AP MLD and a packet at the EMLSR client. AP MLD and EMLSR client are expected to get
1285 // access at the same time because backoff counter is zero and EDCA boundaries are aligned
1286 Simulator::Schedule(MilliSeconds(5), [=, this]() {
1287 uint8_t prio = (switchToOtherLink ? 3 : 0);
1288 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 0, 1, 500, prio));
1289 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1290 GetApplication(UPLINK, 0, 1, 500, prio));
1291 });
1292
1293 m_switchFrom.reset();
1294 m_switchTo.reset();
1295
1296 m_events.emplace_back(
1298 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1299 const auto phyHdrDuration = WifiPhy::CalculatePhyPreambleAndHeaderDuration(txVector);
1300 const auto txDuration =
1302 txVector,
1303 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
1304 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
1305
1306 // compute channel switch delay based on the scenario to test
1307 Time channelSwitchDelay{0};
1308 const auto margin = MicroSeconds(2);
1309
1310 switch (m_csdIndex)
1311 {
1313 channelSwitchDelay = MicroSeconds(1);
1314 break;
1315 case BEFORE_PHY_HDR_END:
1316 channelSwitchDelay = phyHdrDuration - margin;
1317 break;
1318 case BEFORE_MAC_HDR_END:
1319 channelSwitchDelay = phyHdrDuration + margin;
1320 break;
1322 channelSwitchDelay = txDuration - m_paddingDelay.at(0) - margin;
1323 break;
1324 case BEFORE_PADDING_END:
1325 channelSwitchDelay = txDuration - m_paddingDelay.at(0) + margin;
1326 break;
1327 default:;
1328 }
1329
1330 NS_TEST_ASSERT_MSG_EQ(channelSwitchDelay.IsStrictlyPositive(),
1331 true,
1332 m_testStr << ": Channel switch delay is not strictly positive ("
1333 << channelSwitchDelay.As(Time::US) << ")");
1334 NS_TEST_ASSERT_MSG_LT(channelSwitchDelay,
1335 m_paddingDelay.at(0),
1336 m_testStr
1337 << ": Channel switch delay is greater than padding delay");
1338 // set channel switch delay
1339 mainPhy->SetAttribute("ChannelSwitchDelay", TimeValue(channelSwitchDelay));
1340
1341 const auto startTx = Simulator::Now();
1342
1343 // check that main PHY has started switching
1344 Simulator::ScheduleNow([=, this]() {
1346 true,
1347 m_testStr << ": Main PHY did not start switching");
1349 +m_mainPhyId,
1350 m_testStr << ": Main PHY did not left the preferred link");
1352 startTx,
1353 m_testStr
1354 << ": Main PHY did not start switching at ICF TX start");
1355 });
1356
1357 // check what happens after channel switch is completed
1358 Simulator::Schedule(channelSwitchDelay + TimeStep(1), [=, this]() {
1359 // sanity check that the channel switch delay was computed correctly
1360 auto auxPhy = m_staMacs[0]->GetWifiPhy(linkId);
1361 auto fem = m_staMacs[0]->GetFrameExchangeManager(linkId);
1362 switch (m_csdIndex)
1363 {
1364 case BEFORE_PADDING_END:
1367 startTx + txDuration - m_paddingDelay.at(0),
1368 m_testStr << ": Channel switch terminated before padding start");
1369 [[fallthrough]];
1371 if (useMacHdrInfo)
1372 {
1373 NS_TEST_EXPECT_MSG_EQ(fem->GetReceivedMacHdr().has_value(),
1374 true,
1375 m_testStr << ": Channel switch terminated before "
1376 "MAC header info is received");
1377 }
1378 [[fallthrough]];
1379 case BEFORE_MAC_HDR_END:
1380 NS_TEST_EXPECT_MSG_EQ(fem->GetOngoingRxInfo().has_value(),
1381 true,
1382 m_testStr << ": Channel switch terminated before "
1383 "receiving RXSTART indication");
1384 break;
1385 case BEFORE_PHY_HDR_END:
1386 NS_TEST_EXPECT_MSG_EQ(auxPhy->GetInfoIfRxingPhyHeader().has_value(),
1387 true,
1388 m_testStr << ": Expected to be receiving the PHY header");
1389 break;
1391 NS_TEST_EXPECT_MSG_EQ(auxPhy->GetTimeToPreambleDetectionEnd().has_value(),
1392 true,
1393 m_testStr
1394 << ": Expected to be in preamble detection period");
1395 default:
1396 NS_ABORT_MSG("Unexpected channel switch duration index");
1397 }
1398
1399 // if the main PHY switched to the same link on which the ICF is being received,
1400 // connecting the main PHY to the link is postponed until the end of the ICF, hence
1401 // the main PHY is not operating on any link at this time;
1402 // if the main PHY switched to another link, it was connected to that link but
1403 // the UL TXOP did not start because, at the end of the NAV and CCA busy in the last
1404 // PIFS check, it was detected that a frame which could be an ICF was being received
1405 // on another link)
1406 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetLinkForPhy(m_mainPhyId).has_value(),
1407 switchToOtherLink,
1408 m_testStr
1409 << ": Main PHY not expected to be connected to a link");
1410
1411 if (switchToOtherLink)
1412 {
1414 +m_staMacs[0]->GetLinkForPhy(m_mainPhyId).value(),
1416 m_testStr << ": Main PHY did not left the link on which TID 3 is mapped");
1417 }
1418 });
1419
1420 // check what happens when the ICF ends
1421 Simulator::Schedule(txDuration + TimeStep(1), [=, this]() {
1422 // if the main PHY switched to the same link on which the ICF has been received,
1423 // it has now been connected to that link; if the main PHY switched to another
1424 // link and there was not enough time for the main PHY to start switching to the
1425 // link on which the ICF has been received at the start of the padding, the ICF
1426 // has been dropped and the main PHY stayed on the preferred link
1427
1428 const auto id = m_staMacs[0]->GetLinkForPhy(m_mainPhyId);
1429 NS_TEST_EXPECT_MSG_EQ(id.has_value(),
1430 true,
1431 m_testStr << ": Main PHY expected to be connected to a link");
1432 NS_TEST_EXPECT_MSG_EQ(+id.value(),
1433 +linkId,
1434 m_testStr << ": Main PHY connected to an unexpected link");
1435
1437 true,
1438 m_testStr << ": Main PHY was not connected to a link");
1440 +linkId,
1441 m_testStr
1442 << ": Main PHY was not connected to the expected link");
1444 Simulator::Now() - TimeStep(1),
1445 m_testStr << ": Main PHY was not connected at ICF TX end");
1446 });
1447 });
1448
1449 m_events.emplace_back(
1451 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1452 const auto id = m_staMacs[0]->GetLinkForPhy(m_mainPhyId);
1453 NS_TEST_EXPECT_MSG_EQ(id.has_value(),
1454 true,
1455 m_testStr << ": Main PHY expected to be connected to a link");
1456 NS_TEST_EXPECT_MSG_EQ(+id.value(),
1457 +linkId,
1458 m_testStr
1459 << ": Main PHY expected to be connected to same link as ICF");
1460 Simulator::ScheduleNow([=, this]() {
1461 NS_TEST_EXPECT_MSG_EQ(m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId)->IsStateTx(),
1462 true,
1463 m_testStr << ": Main PHY expected to be transmitting");
1464 });
1465
1466 const auto txDuration =
1468 txVector,
1469 m_staMacs[0]->GetWifiPhy(linkId)->GetPhyBand());
1470
1471 Simulator::ScheduleNow([=, this]() {
1472 CheckInDeviceInterference(m_testStr + ", CTS", linkId, txDuration);
1473 });
1474 });
1475
1476 m_events.emplace_back(WIFI_MAC_QOSDATA);
1477 m_events.emplace_back(
1479 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1480 const auto txDuration =
1482 txVector,
1483 m_staMacs[0]->GetWifiPhy(linkId)->GetPhyBand());
1484
1485 Simulator::ScheduleNow([=, this]() {
1486 CheckInDeviceInterference(m_testStr + ", ACK", linkId, txDuration);
1487 });
1488 // check the KeepMainPhyAfterDlTxop attribute
1489 Simulator::Schedule(txDuration + TimeStep(1), [=, this]() {
1490 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
1491 auto shouldSwitch = (!keepMainPhyAfterDlTxop || switchToOtherLink);
1492 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
1493 shouldSwitch,
1494 m_testStr << ": Main PHY should "
1495 << (shouldSwitch ? "" : "not")
1496 << " be switching back after DL TXOP end");
1497 });
1498 });
1499
1500 // Uplink TXOP
1501 m_events.emplace_back(
1503 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1504 const auto txDuration =
1506 txVector,
1507 m_staMacs[0]->GetWifiPhy(linkId)->GetPhyBand());
1508
1509 Simulator::ScheduleNow([=, this]() {
1510 CheckInDeviceInterference(m_testStr + ", QoS Data", linkId, txDuration);
1511 });
1512 });
1513
1514 m_events.emplace_back(
1516 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
1517 const auto txDuration =
1519 txVector,
1520 m_staMacs[0]->GetWifiPhy(linkId)->GetPhyBand());
1521 // check that main PHY switches back after UL TXOP
1522 Simulator::Schedule(txDuration + TimeStep(1), [=, this]() {
1523 auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
1525 mainPhy->IsStateSwitching(),
1526 true,
1527 m_testStr << ": Main PHY should be switching back after UL TXOP end");
1528 });
1529 // Continue with the next test scenario
1530 Simulator::Schedule(MilliSeconds(2), [=, this]() {
1531 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
1532 m_csdIndex = static_cast<ChannelSwitchEnd>(static_cast<uint8_t>(m_csdIndex) + 1);
1533 if (m_csdIndex == CSD_COUNT)
1534 {
1535 ++m_testIndex;
1537 }
1538
1539 if (m_testIndex < 8)
1540 {
1541 RunOne();
1542 }
1543 });
1544 });
1545}
1546
1548 : EmlsrOperationsTestBase("Check handling of the switch main PHY back timer")
1549{
1550 m_mainPhyId = 2;
1551 m_linksToEnableEmlsrOn = {0, 1, 2};
1552 m_nEmlsrStations = 1;
1554
1555 // channel switch delay will be also set to 64 us
1558 m_establishBaDl = {0};
1559 m_establishBaUl = {0, 4};
1560 m_duration = Seconds(0.5);
1561}
1562
1563void
1565{
1566 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(64)));
1567 Config::SetDefault("ns3::WifiPhy::NotifyMacHdrRxEnd", BooleanValue(true));
1568 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(false));
1569 Config::SetDefault("ns3::EmlsrManager::AuxPhyTxCapable", BooleanValue(false));
1570 // Use only link 1 for DL and UL traffic
1571 std::string mapping =
1572 "0 " + std::to_string(m_linkIdForTid0) + "; 4 " + std::to_string(m_linkIdForTid4);
1573 Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingDl", StringValue(mapping));
1574 Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingUl", StringValue(mapping));
1575 Config::SetDefault("ns3::EmlsrManager::AuxPhyMaxModClass", StringValue("HT"));
1576 Config::SetDefault("ns3::AdvancedEmlsrManager::UseAuxPhyCca", BooleanValue(true));
1577
1579
1582 hdr.SetAddr2(m_apMac->GetFrameExchangeManager(m_linkIdForTid0)->GetAddress());
1583 hdr.SetAddr3(m_apMac->GetAddress());
1584 hdr.SetDsFrom();
1585 hdr.SetDsNotTo();
1586 hdr.SetQosTid(0);
1587
1589}
1590
1591void
1593 uint8_t phyId,
1594 WifiConstPsduMap psduMap,
1595 WifiTxVector txVector,
1596 double txPowerW)
1597{
1598 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
1599
1600 const auto psdu = psduMap.cbegin()->second;
1601 const auto& hdr = psdu->GetHeader(0);
1602
1603 // nothing to do before setup is completed
1604 if (!m_setupDone)
1605 {
1606 return;
1607 }
1608
1609 auto linkId = mac->GetLinkForPhy(phyId);
1610 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(),
1611 true,
1612 "PHY " << +phyId << " is not operating on any link");
1613
1614 if (!m_events.empty())
1615 {
1616 // check that the expected frame is being transmitted
1617 NS_TEST_EXPECT_MSG_EQ(m_events.front().hdrType,
1618 hdr.GetType(),
1619 "Unexpected MAC header type for frame #" << ++m_processedEvents);
1620 // perform actions/checks, if any
1621 if (m_events.front().func)
1622 {
1623 m_events.front().func(psdu, txVector, linkId.value());
1624 }
1625
1626 m_events.pop_front();
1627 }
1628}
1629
1630void
1636
1637void
1639{
1642
1643 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
1644
1646}
1647
1648void
1650 const EmlsrMainPhySwitchTrace& info)
1651{
1653
1654 if (!m_setupDone)
1655 {
1656 return;
1657 }
1658
1659 if (!m_dlPktDone && info.GetName() == "UlTxopAuxPhyNotTxCapable")
1660 {
1661 NS_LOG_INFO("Main PHY starts switching\n");
1662 const auto delay =
1664 ? Time{0}
1665 : MicroSeconds(30); // greater than duration of PHY header of non-HT PPDU
1666 Simulator::Schedule(delay,
1667 [=, this]() { m_apMac->GetQosTxop(AC_BE)->Queue(m_bcastFrame); });
1668 return;
1669 }
1670
1671 // lambda to generate a frame with TID 4 and to handle the corresponding frames
1672 auto genTid4Frame = [=, this]() {
1673 m_dlPktDone = true;
1674
1675 // in 5 microseconds, while still switching, generate a packet with TID 4, which causes a
1676 // channel access request on link 0; if switching can be interrupted, the main PHY starts
1677 // switching to link 0 as soon as channel access is gained on link 0
1678 Simulator::Schedule(MicroSeconds(5), [=, this]() {
1679 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
1680 GetApplication(UPLINK, 0, 1, 500, 4));
1681 // channel access can be obtained within a slot due to slot alignment
1682 Simulator::Schedule(m_apMac->GetWifiPhy(m_linkIdForTid4)->GetSlot(), [=, 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");
1689 NS_TEST_EXPECT_MSG_EQ(
1690 +advEmlsrMgr->m_mainPhySwitchInfo.to,
1691 +(advEmlsrMgr->m_interruptSwitching ? m_linkIdForTid4 : m_mainPhyId),
1692 "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 phyHdrDuration = WifiPhy::CalculatePhyPreambleAndHeaderDuration(txVector);
2199 const auto txDuration =
2201 txVector,
2202 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2203
2204 if (testIndex == NON_HT_PPDU_DONT_USE_MAC_HDR)
2205 {
2206 // main PHY is expected to switch back when the UL TXOP ends
2208 }
2209 else
2210 {
2211 Simulator::Schedule(MilliSeconds(2), [=, this]() {
2212 // check that trace infos have been received
2214 true,
2215 "Did not receive the expected trace infos");
2216 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
2217
2218 if (++m_testIndex < static_cast<uint8_t>(COUNT))
2219 {
2220 RunOne();
2221 }
2222 });
2223 }
2224 });
2225}
2226
2228 : EmlsrOperationsTestBase("Verify operations during the NAV and CCA check in the last PIFS")
2229{
2230 m_mainPhyId = 1;
2231 m_linksToEnableEmlsrOn = {0, 1, 2};
2232 m_nEmlsrStations = 1;
2234
2237 m_establishBaDl = {};
2238 m_establishBaUl = {0};
2239 m_duration = Seconds(0.5);
2240}
2241
2242void
2244{
2245 Config::SetDefault("ns3::EmlsrManager::AuxPhyTxCapable", BooleanValue(false));
2246 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(false));
2247 Config::SetDefault("ns3::ChannelAccessManager::NSlotsLeft", UintegerValue(m_nSlotsLeft));
2248 // Use only one link for DL and UL traffic
2249 std::string mapping = "0 " + std::to_string(m_linkIdForTid0);
2250 Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingDl", StringValue(mapping));
2251 Config::SetDefault("ns3::EhtConfiguration::TidToLinkMappingUl", StringValue(mapping));
2252 Config::SetDefault("ns3::AdvancedEmlsrManager::UseAuxPhyCca", BooleanValue(true));
2253 Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth", UintegerValue(20));
2254
2255 // use 40 MHz channels
2256 m_channelsStr = {"{3, 40, BAND_2_4GHZ, 0}"s,
2257 "{38, 40, BAND_5GHZ, 0}"s,
2258 "{3, 40, BAND_6GHZ, 0}"s};
2259
2261}
2262
2263void
2265 uint8_t phyId,
2266 WifiConstPsduMap psduMap,
2267 WifiTxVector txVector,
2268 double txPowerW)
2269{
2270 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2271
2272 const auto psdu = psduMap.cbegin()->second;
2273 const auto& hdr = psdu->GetHeader(0);
2274
2275 // nothing to do before setup is completed
2276 if (!m_setupDone)
2277 {
2278 return;
2279 }
2280
2281 auto linkId = mac->GetLinkForPhy(phyId);
2282 NS_TEST_ASSERT_MSG_EQ(linkId.has_value(),
2283 true,
2284 "PHY " << +phyId << " is not operating on any link");
2285
2286 if (!m_events.empty())
2287 {
2288 // check that the expected frame is being transmitted
2289 NS_TEST_EXPECT_MSG_EQ(m_events.front().hdrType,
2290 hdr.GetType(),
2291 "Unexpected MAC header type for frame #" << ++m_processedEvents);
2292 // perform actions/checks, if any
2293 if (m_events.front().func)
2294 {
2295 m_events.front().func(psdu, txVector, linkId.value());
2296 }
2297
2298 m_events.pop_front();
2299 }
2300}
2301
2302void
2304{
2305 Simulator::Schedule(MilliSeconds(5), [=, this]() {
2306 m_setupDone = true;
2307 RunOne();
2308 });
2309}
2310
2311void
2313{
2316
2317 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
2318
2320}
2321
2322void
2324{
2325 const auto useAuxPhyCca = ((m_testIndex & 0x1) == 1);
2326 const auto scenario = static_cast<TestScenario>(m_testIndex >> 1);
2327
2328 // aux PHY operating on the link on which TID 0 is mapped
2329 const auto auxPhy = m_staMacs[0]->GetDevice()->GetPhy(m_linkIdForTid0);
2330 const auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
2331 const auto slot = auxPhy->GetSlot();
2332 const auto pifs = auxPhy->GetSifs() + slot;
2333 const auto timeToBackoffEnd = slot * m_nSlotsLeft;
2334 NS_TEST_ASSERT_MSG_GT(timeToBackoffEnd, pifs + slot, "m_nSlotsLeft too small for this test");
2335
2336 const auto switchDelay =
2338 ? timeToBackoffEnd + slot
2339 : (scenario == LESS_THAN_PIFS_UNTIL_BACKOFF_END ? timeToBackoffEnd - pifs + slot
2340 : timeToBackoffEnd - pifs - slot));
2341
2342 m_staMacs[0]->GetEmlsrManager()->SetAttribute("UseAuxPhyCca", BooleanValue(useAuxPhyCca));
2343 mainPhy->SetAttribute("ChannelSwitchDelay", TimeValue(switchDelay));
2344
2345 NS_LOG_INFO("Starting test #" << m_testIndex << "\n");
2346
2347 // the AP sends a broadcast frame on the link on which TID 0 is mapped
2350 hdr.SetAddr2(m_apMac->GetFrameExchangeManager(m_linkIdForTid0)->GetAddress());
2351 hdr.SetAddr3(m_apMac->GetAddress());
2352 hdr.SetDsFrom();
2353 hdr.SetDsNotTo();
2354 hdr.SetQosTid(0);
2355
2356 m_apMac->GetQosTxop(AC_BE)->Queue(Create<WifiMpdu>(Create<Packet>(1000), hdr));
2357
2358 m_events.emplace_back(
2360 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
2361 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr1(),
2363 "Expected a broadcast frame");
2364 NS_TEST_EXPECT_MSG_EQ(+linkId,
2366 "Broadcast frame transmitted on wrong link");
2367 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
2368 m_apMac->GetFrameExchangeManager(linkId)->GetAddress(),
2369 "Unexpected TA for the broadcast frame");
2370 const auto txDuration =
2372 txVector,
2373 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2374 const auto emlsrBeEdca = m_staMacs[0]->GetQosTxop(AC_BE);
2375
2376 // generate a packet at the EMLSR client while the medium on the link on which TID 0
2377 // is mapped is still busy, so that a backoff value is generated. The backoff counter
2378 // is configured to be equal to the m_nSlotsLeft value
2379 Simulator::Schedule(txDuration - TimeStep(1), [=, this]() {
2380 emlsrBeEdca->StartBackoffNow(m_nSlotsLeft, m_linkIdForTid0);
2381 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
2382 GetApplication(UPLINK, 0, 1, 500));
2383 });
2384
2385 // given that the backoff counter equals m_nSlotsLeft, we expect that, an AIFS after the
2386 // end of the broadcast frame transmission, the NSlotsLeftAlert trace is fired and the
2387 // main PHY starts switching to the link on which TID 0 is mapped
2388 const auto aifs = auxPhy->GetSifs() + emlsrBeEdca->GetAifsn(m_linkIdForTid0) * slot;
2389 Simulator::Schedule(txDuration + aifs + TimeStep(1), [=, this]() {
2390 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
2391 true,
2392 "Expected the main PHY to be switching");
2393 NS_TEST_EXPECT_MSG_EQ(mainPhy->GetDelayUntilIdle(),
2394 switchDelay - TimeStep(1),
2395 "Unexpected end of main PHY channel switch");
2396
2397 const auto now = Simulator::Now();
2398 switch (scenario)
2399 {
2401 if (!useAuxPhyCca)
2402 {
2403 m_expectedTxStart = now + mainPhy->GetDelayUntilIdle() + pifs;
2405 }
2406 else
2407 {
2408 m_expectedTxStart = now + mainPhy->GetDelayUntilIdle();
2410 }
2411 break;
2413 if (!useAuxPhyCca)
2414 {
2415 m_expectedTxStart = now + mainPhy->GetDelayUntilIdle() + pifs;
2417 }
2418 else
2419 {
2421 ->GetChannelAccessManager(m_linkIdForTid0)
2422 ->GetBackoffEndFor(emlsrBeEdca);
2424 }
2425 break;
2428 ->GetChannelAccessManager(m_linkIdForTid0)
2429 ->GetBackoffEndFor(emlsrBeEdca);
2431 break;
2432 default:
2433 NS_ABORT_MSG("Unexpected scenario " << +static_cast<uint8_t>(scenario));
2434 }
2435 });
2436 });
2437
2438 m_events.emplace_back(
2440 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
2441 NS_TEST_EXPECT_MSG_EQ(+linkId,
2443 "Unicast frame transmitted on wrong link");
2444 NS_TEST_EXPECT_MSG_EQ(psdu->GetAddr2(),
2445 m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress(),
2446 "Unexpected TA for the unicast frame");
2449 "Unexpected start TX time for unicast frame");
2451 txVector.GetChannelWidth(),
2452 "Unexpected channel width for the unicast frame");
2453 });
2454
2455 m_events.emplace_back(
2457 [=, this](Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector, uint8_t linkId) {
2458 Simulator::Schedule(MilliSeconds(2), [=, this]() {
2459 NS_TEST_EXPECT_MSG_EQ(m_events.empty(), true, "Not all events took place");
2460
2461 if (++m_testIndex < static_cast<uint8_t>(COUNT) * 2)
2462 {
2463 RunOne();
2464 }
2465 });
2466 });
2467}
2468
2470 : TestSuite("wifi-emlsr-link-switch", Type::UNIT)
2471{
2472 for (bool switchAuxPhy : {true, false})
2473 {
2474 for (bool resetCamStateAndInterruptSwitch : {true, false})
2475 {
2476 for (auto auxPhyMaxChWidth : {MHz_u{20}, MHz_u{40}, MHz_u{80}, MHz_u{160}})
2477 {
2479 {switchAuxPhy, resetCamStateAndInterruptSwitch, auxPhyMaxChWidth}),
2480 TestCase::Duration::QUICK);
2481 }
2482 }
2483 }
2484
2485 AddTestCase(new EmlsrCheckNavAndCcaLastPifsTest(), TestCase::Duration::QUICK);
2486 AddTestCase(new EmlsrIcfSentDuringMainPhySwitchTest(), TestCase::Duration::QUICK);
2487 AddTestCase(new EmlsrSwitchMainPhyBackTest(), TestCase::Duration::QUICK);
2488
2489 AddTestCase(new EmlsrCcaBusyTest(MHz_u{20}), TestCase::Duration::QUICK);
2490 AddTestCase(new EmlsrCcaBusyTest(MHz_u{80}), TestCase::Duration::QUICK);
2491
2492 for (const auto switchAuxPhy : {true, false})
2493 {
2494 for (const auto auxPhyTxCapable : {true, false})
2495 {
2496 AddTestCase(new SingleLinkEmlsrTest(switchAuxPhy, auxPhyTxCapable),
2497 TestCase::Duration::QUICK);
2498 }
2499 }
2500}
2501
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 uint16_t MEDIUM_SYNC_THRESHOLD_USEC
The aMediumSyncThreshold defined by Sec. 35.3.16.18.1 of 802.11be D4.0.
static constexpr bool DONT_REQUEST_ACCESS
do not request channel access when PHY switch ends
static WifiMode GetHtMcs0()
Return MCS 0 from HT MCS values.
handles interference calculations
Time GetEnergyDuration(Watt_u energy, const WifiSpectrumBandInfo &band)
void SetList(const std::list< uint64_t > &packetlist)
static Mac48Address GetBroadcast()
static WifiMode GetOfdmRate6Mbps()
Return a WifiMode for OFDM at 6 Mbps.
AttributeValue implementation for Pointer.
Ptr< T > Get() const
Definition pointer.h:223
Smart pointer class similar to boost::intrusive_ptr.
Template class for packet Queues.
Definition queue.h:257
static EventId Schedule(const Time &delay, FUNC f, Ts &&... args)
Schedule an event to expire after delay.
Definition simulator.h:561
static void Destroy()
Execute the events scheduled with ScheduleDestroy().
Definition simulator.cc:131
static Time Now()
Return the current simulation virtual time.
Definition simulator.cc:197
static void Run()
Run the simulation.
Definition simulator.cc:167
static EventId ScheduleNow(FUNC f, Ts &&... args)
Schedule an event to expire Now.
Definition simulator.h:595
static void Stop()
Tell the Simulator the calling event should be the last one executed.
Definition simulator.cc:175
Hold variables of type string.
Definition string.h:45
void AddTestCase(TestCase *testCase, Duration duration=Duration::QUICK)
Add an individual child TestCase to this test suite.
Definition test.cc:292
A suite of tests to run.
Definition test.h:1267
Type
Type of test.
Definition test.h:1274
Simulation virtual time values and global simulation resolution.
Definition nstime.h:94
@ US
microsecond
Definition nstime.h:107
@ NS
nanosecond
Definition nstime.h:108
AttributeValue implementation for Time.
Definition nstime.h:1432
Hold an unsigned integer type.
Definition uinteger.h:34
static std::pair< CategoryValue, ActionValue > Peek(Ptr< const Packet > pkt)
Peek an Action header from the given packet.
Implements the IEEE 802.11 MAC header.
void SetAddr1(Mac48Address address)
Fill the Address 1 field with the given address.
void SetQosTid(uint8_t tid)
Set the TID for the QoS header.
void SetDsFrom()
Set the From DS bit in the Frame Control field.
void SetAddr2(Mac48Address address)
Fill the Address 2 field with the given address.
void SetAddr3(Mac48Address address)
Fill the Address 3 field with the given address.
void SetDsNotTo()
Un-set the To DS bit in the Frame Control field.
AttributeValue implementation for WifiMode.
Definition wifi-mode.h:246
std::tuple< uint8_t, MHz_u, WifiPhyBand, uint8_t > ChannelTuple
Tuple identifying a segment of an operating channel.
Definition wifi-phy.h:937
static Time CalculateTxDuration(uint32_t size, const WifiTxVector &txVector, WifiPhyBand band, uint16_t staId=SU_STA_ID)
Definition wifi-phy.cc:1563
static Time CalculatePhyPreambleAndHeaderDuration(const WifiTxVector &txVector)
Definition wifi-phy.cc:1556
This class mimics the TXVECTOR which is to be passed to the PHY in order to define the parameters whi...
WifiMode GetMode(uint16_t staId=SU_STA_ID) const
If this TX vector is associated with an SU PPDU, return the selected payload transmission mode.
MHz_u GetChannelWidth() const
#define NS_ASSERT_MSG(condition, message)
At runtime, in debugging builds, if this condition is not true, the program prints the message to out...
Definition assert.h:75
void SetDefault(std::string name, const AttributeValue &value)
Definition config.cc:886
#define NS_ABORT_MSG(msg)
Unconditional abnormal program termination with a message.
Definition abort.h:38
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition log.h:191
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
Definition log.h:257
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition log.h:264
Ptr< T > CreateObject(Args &&... args)
Create an object by type, with varying number of constructor parameters.
Definition object.h:619
Ptr< T > Create(Ts &&... args)
Create class instances by constructors with varying numbers of arguments and return them by Ptr.
Definition ptr.h:436
#define NS_TEST_EXPECT_MSG_GT_OR_EQ(actual, limit, msg)
Test that an actual value is greater than or equal to limit and report if not.
Definition test.h:986
#define NS_TEST_ASSERT_MSG_LT(actual, limit, msg)
Test that an actual value is less than a limit and report and abort if not.
Definition test.h:699
#define NS_TEST_ASSERT_MSG_EQ(actual, limit, msg)
Test that an actual and expected (limit) value are equal and report and abort if not.
Definition test.h:134
#define NS_TEST_EXPECT_MSG_LT_OR_EQ(actual, limit, msg)
Test that an actual value is less than or equal to a limit and report if not.
Definition test.h:820
#define NS_TEST_EXPECT_MSG_LT(actual, limit, msg)
Test that an actual value is less than a limit and report if not.
Definition test.h:780
#define NS_TEST_EXPECT_MSG_GT(actual, limit, msg)
Test that an actual value is greater than a limit and report if not.
Definition test.h:946
#define NS_TEST_EXPECT_MSG_NE(actual, limit, msg)
Test that an actual and expected (limit) value are not equal and report if not.
Definition test.h:656
#define NS_TEST_EXPECT_MSG_EQ(actual, limit, msg)
Test that an actual and expected (limit) value are equal and report if not.
Definition test.h:241
#define NS_TEST_ASSERT_MSG_NE(actual, limit, msg)
Test that an actual and expected (limit) value are not equal and report and abort if not.
Definition test.h:554
#define NS_TEST_ASSERT_MSG_GT(actual, limit, msg)
Test that an actual value is greater than a limit and report and abort if not.
Definition test.h:864
#define NS_TEST_ASSERT_MSG_GT_OR_EQ(actual, limit, msg)
Test that an actual value is greater than or equal to a limit and report and abort if not.
Definition test.h:905
Time MicroSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1369
Time NanoSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1381
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition nstime.h:1345
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1357
@ WIFI_PHY_BAND_6GHZ
The 6 GHz band.
@ WIFI_PHY_BAND_2_4GHZ
The 2.4 GHz band.
@ WIFI_PHY_BAND_5GHZ
The 5 GHz band.
@ WIFI_CHANLIST_PRIMARY
@ AC_BE
Best Effort.
Definition qos-utils.h:64
Every class exported by the ns3 library is enclosed in the ns3 namespace.
Callback< R, Args... > MakeCallback(R(T::*memPtr)(Args...), OBJ objPtr)
Build Callbacks for class method members which take varying numbers of arguments and potentially retu...
Definition callback.h:684
Ptr< T1 > DynamicCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:580
@ WIFI_MAC_CTL_TRIGGER
@ WIFI_MAC_CTL_RTS
@ WIFI_MAC_CTL_CTS
@ WIFI_MAC_MGT_ACTION
@ WIFI_MAC_MGT_ASSOCIATION_RESPONSE
@ WIFI_MAC_CTL_ACK
@ WIFI_MAC_MGT_ASSOCIATION_REQUEST
@ WIFI_MAC_CTL_BACKRESP
@ WIFI_MAC_CTL_END
@ WIFI_MAC_QOSDATA
Watt_u DbmToW(dBm_u val)
Convert from dBm to Watts.
Definition wifi-utils.cc:34
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)
Definition wifi-mode.h:24
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...