A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
wifi-emlsr-basic-exchanges-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/log.h"
17#include "ns3/mgt-action-headers.h"
18#include "ns3/qos-txop.h"
19#include "ns3/rr-multi-user-scheduler.h"
20#include "ns3/simulator.h"
21#include "ns3/string.h"
22#include "ns3/wifi-net-device.h"
23
24#include <algorithm>
25#include <functional>
26#include <iomanip>
27
28using namespace ns3;
29
30NS_LOG_COMPONENT_DEFINE("WifiEmlsrBasicExchangesTest");
31
33 : EmlsrOperationsTestBase("Check EML DL TXOP transmissions (" +
34 std::to_string(params.nEmlsrStations) + "," +
35 std::to_string(params.nNonEmlsrStations) + ")"),
36 m_emlsrLinks(params.linksToEnableEmlsrOn),
37 m_emlsrEnabledTime(0),
38 m_fe2to3delay(MilliSeconds(20)),
39 m_countQoSframes(0),
40 m_countBlockAck(0)
41{
42 m_nEmlsrStations = params.nEmlsrStations;
43 m_nNonEmlsrStations = params.nNonEmlsrStations;
44 m_linksToEnableEmlsrOn = {}; // do not enable EMLSR right after association
45 m_mainPhyId = 1;
46 m_paddingDelay = params.paddingDelay;
47 m_transitionDelay = params.transitionDelay;
48 m_transitionTimeout = params.transitionTimeout;
49 m_establishBaDl = {0};
50 m_putAuxPhyToSleep = params.putAuxPhyToSleep;
51 m_duration = Seconds(1.5);
52
53 NS_ABORT_MSG_IF(params.linksToEnableEmlsrOn.size() < 2,
54 "This test requires at least two links to be configured as EMLSR links");
55}
56
57void
59 uint8_t phyId,
60 WifiConstPsduMap psduMap,
61 WifiTxVector txVector,
62 double txPowerW)
63{
64 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
65 auto linkId = m_txPsdus.back().linkId;
66
67 auto psdu = psduMap.begin()->second;
68 auto nodeId = mac->GetDevice()->GetNode()->GetId();
69
70 switch (psdu->GetHeader(0).GetType())
71 {
73 NS_ASSERT_MSG(nodeId > 0, "APs do not send AssocReq frames");
74 if (nodeId <= m_nEmlsrStations)
75 {
76 NS_TEST_EXPECT_MSG_EQ(+linkId, +m_mainPhyId, "AssocReq not sent by the main PHY");
77 // this AssocReq is being sent by an EMLSR client. The other EMLSR links should be
78 // in powersave mode after association; we let the non-EMLSR links transition to
79 // active mode (by sending data null frames) after association
80 for (const auto id : m_staMacs.at(nodeId - 1)->GetLinkIds())
81 {
82 if (id != linkId && m_emlsrLinks.count(id) == 1)
83 {
84 m_staMacs[nodeId - 1]->SetPowerSaveMode({true, id});
85 }
86 }
87 }
88 break;
89
91 auto [category, action] = WifiActionHeader::Peek(psdu->GetPayload(0));
92
93 if ((category == WifiActionHeader::PROTECTED_EHT) &&
94 (action.protectedEhtAction ==
96 {
97 nodeId == 0 ? CheckApEmlNotificationFrame(*psdu->begin(), txVector, linkId)
98 : CheckStaEmlNotificationFrame(*psdu->begin(), txVector, linkId);
99 }
100 else if (category == WifiActionHeader::BLOCK_ACK &&
102 {
103 CheckPmModeAfterAssociation(psdu->GetAddr1());
104 }
105 }
106 break;
107
109 CheckInitialControlFrame(*psdu->begin(), txVector, linkId);
110 break;
111
112 case WIFI_MAC_QOSDATA:
113 CheckQosFrames(psduMap, txVector, linkId);
114 break;
115
117 CheckBlockAck(psduMap, txVector, phyId);
118 break;
119
120 case WIFI_MAC_CTL_END:
121 if (auto apMac = DynamicCast<ApWifiMac>(mac))
122 {
123 const auto txDuration =
125 txVector,
126 apMac->GetDevice()->GetPhy(phyId)->GetPhyBand());
127 for (std::size_t i = 0; i < m_nEmlsrStations; ++i)
128 {
129 if (m_staMacs[i]->IsEmlsrLink(linkId) &&
130 m_staMacs[i]->GetWifiPhy(linkId) ==
131 m_staMacs[i]->GetDevice()->GetPhy(m_mainPhyId))
132 {
133 // AP is terminating a TXOP on an EMLSR link on which the main PHY is operating,
134 // aux PHYs should resume from sleep
135 Simulator::Schedule(txDuration + TimeStep(1),
137 this,
138 m_staMacs[i],
139 false);
140 }
141 }
142 }
143 break;
144
145 default:;
146 }
147}
148
149void
151{
152 // Channel switch delay should be less than the ICF padding duration, otherwise
153 // DL TXOPs cannot be initiated on auxiliary links
154 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(75)));
155
157
159 for (std::size_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
160 {
161 m_apMac->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
162 }
163
164 m_apMac->GetQosTxop(AC_BE)->SetTxopLimits(
165 {MicroSeconds(3200), MicroSeconds(3200), MicroSeconds(3200)});
166
168 {
169 auto muScheduler =
171 m_apMac->AggregateObject(muScheduler);
172 for (uint8_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
173 {
174 m_apMac->GetFrameExchangeManager(linkId)->GetAckManager()->SetAttribute(
175 "DlMuAckSequenceType",
177 }
178 }
179}
180
181void
183{
185 {
186 // we are done with association and Block Ack agreement; we can now enable EMLSR mode
187 m_lastAid = 0;
189 return;
190 }
191
192 // we are done with sending EML Operating Mode Notification frames. We can now generate
193 // packets for all non-AP MLDs
194 for (std::size_t i = 0; i < m_nEmlsrStations + m_nNonEmlsrStations; i++)
195 {
196 // when multiple non-AP MLDs are present, MU transmission are used. Given that the
197 // available bandwidth decreases as the number of non-AP MLDs increases, compute the
198 // number of packets to generate so that we always have two A-MPDUs per non-AP MLD
199 std::size_t count = 8 / (m_nEmlsrStations + m_nNonEmlsrStations);
200 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, i, count, 450));
201 }
202
203 // in case of 2 EMLSR clients using no non-EMLSR link, generate one additional short
204 // packet to each EMLSR client to test transition delay
205 if (m_nEmlsrStations == 2 && m_apMac->GetNLinks() == m_emlsrLinks.size())
206 {
208 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 0, 1, 40));
209 m_apMac->GetDevice()->GetNode()->AddApplication(GetApplication(DOWNLINK, 1, 1, 40));
210 });
211 }
212
213 // schedule the transmission of EML Operating Mode Notification frames to disable EMLSR mode
214 // and the generation of other packets destined to the EMLSR clients
215 for (std::size_t id = 0; id < m_nEmlsrStations; id++)
216 {
217 Simulator::Schedule(m_fe2to3delay + MilliSeconds(5 * (id + 1)), [=, this]() {
218 m_staMacs.at(id)->GetEmlsrManager()->SetAttribute(
219 "EmlsrLinkSet",
221 });
222
224 m_apMac->GetDevice()->GetNode()->AddApplication(
226 });
227 }
228}
229
230void
232{
233 m_staMacs.at(m_lastAid)->GetEmlsrManager()->SetAttribute(
234 "EmlsrLinkSet",
236 m_lastAid++;
237 Simulator::Schedule(MilliSeconds(5), [=, this]() {
239 {
240 // make the next STA send EML Notification frame
242 return;
243 }
244 // all stations enabled EMLSR mode; start traffic
246 StartTraffic();
247 });
248}
249
250void
252{
253 auto psduIt = m_txPsdus.cbegin();
254
255 // lambda to jump to the next QoS data frame or MU-RTS Trigger Frame transmitted
256 // to an EMLSR client
257 auto jumpToQosDataOrMuRts = [&]() {
258 while (psduIt != m_txPsdus.cend() &&
259 !psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData())
260 {
261 auto psdu = psduIt->psduMap.cbegin()->second;
262 if (psdu->GetHeader(0).IsTrigger())
263 {
264 CtrlTriggerHeader trigger;
265 psdu->GetPayload(0)->PeekHeader(trigger);
266 if (trigger.IsMuRts())
267 {
268 break;
269 }
270 }
271 psduIt++;
272 }
273 };
274
275 /**
276 * Before enabling EMLSR mode, no MU-RTS TF should be sent. Four packets are generated
277 * after association to trigger the establishment of a Block Ack agreement. The TXOP Limit
278 * and the MCS are set such that two packets can be transmitted in a TXOP, hence we expect
279 * that the AP MLD sends two A-MPDUs to each non-AP MLD.
280 *
281 * EMLSR client with EMLSR mode to be enabled on all links: after ML setup, all other links
282 * stay in power save mode, hence BA establishment occurs on the same link.
283 *
284 * [link 0]
285 * ───────────────────────────────────────────────────────────────────────────
286 * | power save mode
287 *
288 * ┌─────┐ ┌─────┐ ┌───┬───┐ ┌───┬───┐
289 * ┌───┐ │Assoc│ │ADDBA│ ┌───┐ │QoS│QoS│ │QoS│QoS│
290 * [link 1] │ACK│ │Resp │ │ Req │ │ACK│ │ 0 │ 1 │ │ 2 │ 3 │
291 * ───┬─────┬┴───┴──┴─────┴┬───┬─┴─────┴┬───┬─┬─────┬┴───┴─┴───┴───┴┬──┬─┴───┴───┴┬──┬───
292 * │Assoc│ │ACK│ │ACK│ │ADDBA│ │BA│ │BA│
293 * │ Req │ └───┘ └───┘ │Resp │ └──┘ └──┘
294 * └─────┘ └─────┘
295 *
296 * [link 2]
297 * ───────────────────────────────────────────────────────────────────────────
298 * | power save mode
299 *
300 *
301 * EMLSR client with EMLSR mode to be enabled on not all the links: after ML setup,
302 * the other EMLSR links stay in power save mode, the non-EMLSR link (link 1) transitions
303 * to active mode.
304 *
305 * ┌─────┐ ┌───┬───┐
306 * ┌───┐ │ADDBA│ ┌───┐ │QoS│QoS│
307 * [link 0 - non EMLSR] │ACK│ │ Req │ │ACK│ │ 2 │ 3 │
308 * ──────────────────────────────┬────┬┴───┴──┴─────┴┬───┬─┬─────┬┴───┴─┴───┴───┴┬──┬─
309 * │Data│ │ACK│ │ADDBA│ │BA│
310 * │Null│ └───┘ │Resp │ └──┘
311 * └────┘ └─────┘
312 * ┌─────┐ ┌───┬───┐
313 * ┌───┐ │Assoc│ │QoS│QoS│
314 * [link 1] │ACK│ │Resp │ │ 0 │ 1 │
315 * ───┬─────┬┴───┴──┴─────┴┬───┬──────────────────────────────────┴───┴───┴┬──┬───────
316 * │Assoc│ │ACK│ │BA│
317 * │ Req │ └───┘ └──┘
318 * └─────┘
319 *
320 * [link 2]
321 * ───────────────────────────────────────────────────────────────────────────
322 * | power save mode
323 *
324 * Non-EMLSR client (not shown): after ML setup, all other links transition to active mode
325 * by sending a Data Null frame; QoS data frame exchanges occur on two links simultaneously.
326 */
327 for (std::size_t i = 0; i < m_nEmlsrStations + m_nNonEmlsrStations; i++)
328 {
329 std::set<uint8_t> linkIds;
330
331 jumpToQosDataOrMuRts();
332 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend() &&
333 psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData()),
334 true,
335 "Expected at least one QoS data frame before enabling EMLSR mode");
336 linkIds.insert(psduIt->linkId);
337 const auto firstAmpduTxEnd =
338 psduIt->startTx +
339 WifiPhy::CalculateTxDuration(psduIt->psduMap,
340 psduIt->txVector,
341 m_staMacs[i]->GetWifiPhy(psduIt->linkId)->GetPhyBand());
342 auto firstQos = psduIt++;
343
344 jumpToQosDataOrMuRts();
345 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend() &&
346 psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData()),
347 true,
348 "Expected at least two QoS data frames before enabling EMLSR mode");
349 linkIds.insert(psduIt->linkId);
350 const auto secondAmpduTxStart = psduIt->startTx;
351
352 auto beaconInBetween{false};
353 while (++firstQos != psduIt)
354 {
355 if (firstQos->psduMap.cbegin()->second->GetHeader(0).IsBeacon())
356 {
357 beaconInBetween = true;
358 break;
359 }
360 }
361
362 psduIt++;
363
364 /**
365 * If this is an EMLSR client and there is no setup link other than the one used to
366 * establish association that is not an EMLSR link, then the two A-MPDUs are sent one
367 * after another on the link used to establish association.
368 */
369 auto setupLinks = m_staMacs[i]->GetSetupLinkIds();
370
371 bool areAllSetupLinksEmlsr =
372 std::all_of(setupLinks.begin(), setupLinks.end(), [&](auto&& linkId) {
373 return linkId == m_mainPhyId || m_emlsrLinks.contains(linkId);
374 });
375
376 if (i < m_nEmlsrStations && areAllSetupLinksEmlsr)
377 {
378 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
379 1,
380 "Expected both A-MPDUs to be sent on the same link");
381 NS_TEST_EXPECT_MSG_EQ(*linkIds.begin(), +m_mainPhyId, "A-MPDUs sent on incorrect link");
382 NS_TEST_EXPECT_MSG_LT(firstAmpduTxEnd,
383 secondAmpduTxStart,
384 "A-MPDUs are not sent one after another");
385 }
386 /**
387 * Otherwise, the two A-MPDUs can be sent concurrently on two distinct links (may be
388 * the link used to establish association and a non-EMLSR link).
389 */
390 else if (!beaconInBetween)
391 {
392 NS_TEST_EXPECT_MSG_EQ(linkIds.size(),
393 2,
394 "Expected A-MPDUs to be sent on distinct links");
395 NS_TEST_EXPECT_MSG_GT(firstAmpduTxEnd,
396 secondAmpduTxStart,
397 "A-MPDUs are not sent concurrently");
398 }
399 }
400
401 /**
402 * After enabling EMLSR mode, MU-RTS TF should only be sent on EMLSR links. After the exchange
403 * of EML Operating Mode Notification frames, a number of packets are generated at the AP MLD
404 * to prepare two A-MPDUs for each non-AP MLD.
405 *
406 * EMLSR client with EMLSR mode to be enabled on all links (A is the EMLSR client, B is the
407 * non-EMLSR client):
408 * ┌─────┬─────┐
409 * │QoS 4│QoS 5│
410 * │ to A│ to A│
411 * ┌───┐ ├─────┼─────┤
412 * │MU │ │QoS 4│QoS 5│
413 * [link 0] │RTS│ │ to B│ to B│
414 * ──────────────────────────┴───┴┬───┬┴─────┴─────┴┬──┬────────────
415 * │CTS│ │BA│
416 * ├───┤ ├──┤
417 * │CTS│ │BA│
418 * └───┘ └──┘
419 * ┌───┐ ┌─────┬─────┐
420 * ┌───┐ │EML│ │QoS 6│QoS 7│
421 * [link 1] │ACK│ │OM │ │ to B│ to B│
422 * ────┬───┬┴───┴──┴───┴┬───┬─┴─────┴─────┴┬──┬────────────────────────────────────
423 * │EML│ │ACK│ │BA│
424 * │OM │ └───┘ └──┘
425 * └───┘
426 * ┌───┐ ┌─────┬─────┐
427 * │MU │ │QoS 6│QoS 7│
428 * [link 2] │RTS│ │ to A│ to A│
429 * ─────────────────────────────────────────────────────────┴───┴┬───┬┴─────┴─────┴┬──┬─
430 * │CTS│ │BA│
431 * └───┘ └──┘
432 *
433 * EMLSR client with EMLSR mode to be enabled on not all the links (A is the EMLSR client,
434 * B is the non-EMLSR client):
435 * ┌─────┬─────┐
436 * │QoS 4│QoS 5│
437 * │ to A│ to A│
438 * ├─────┼─────┤
439 * │QoS 4│QoS 5│
440 * [link 0 - non EMLSR] │ to B│ to B│
441 * ───────────────────────────┴─────┴─────┴┬──┬───────────────────────────
442 * │BA│
443 * ├──┤
444 * │BA│
445 * └──┘
446 * ┌─────┬─────┐
447 * │QoS 6│QoS 7│
448 * │ to A│ to A│
449 * ┌───┐ ┌───┐ ├─────┼─────┤
450 * ┌───┐ │EML│ │MU │ │QoS 6│QoS 7│
451 * [link 1] │ACK│ │OM │ │RTS│ │ to B│ to B│
452 * ────┬───┬┴───┴──┴───┴┬───┬─┴───┴┬───┬┴─────┴─────┴┬──┬────────────
453 * │EML│ │ACK│ │CTS│ │BA│
454 * │OM │ └───┘ ├───┤ ├──┤
455 * └───┘ │CTS│ │BA│
456 * └───┘ └──┘
457 *
458 * [link 2]
459 * ────────────────────────────────────────────────────────────────────────────────
460 */
461
462 /// Store a QoS data frame or an MU-RTS TF followed by a QoS data frame
463 using FrameExchange = std::list<decltype(psduIt)>;
464
465 std::vector<std::list<FrameExchange>> frameExchanges(m_nEmlsrStations);
466
467 // compute all frame exchanges involving EMLSR clients
468 while (psduIt != m_txPsdus.cend())
469 {
470 jumpToQosDataOrMuRts();
471 if (psduIt == m_txPsdus.cend())
472 {
473 break;
474 }
475
476 if (IsTrigger(psduIt->psduMap))
477 {
478 CtrlTriggerHeader trigger;
479 psduIt->psduMap.cbegin()->second->GetPayload(0)->PeekHeader(trigger);
480 // this is an MU-RTS TF starting a new frame exchange sequence; add it to all
481 // the addressed EMLSR clients
483 true,
484 "jumpToQosDataOrMuRts does not return TFs other than MU-RTS");
485 for (const auto& userInfo : trigger)
486 {
487 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
488 {
489 if (m_staMacs.at(i)->GetAssociationId() == userInfo.GetAid12())
490 {
491 frameExchanges.at(i).emplace_back(FrameExchange{psduIt});
492 break;
493 }
494 }
495 }
496 psduIt++;
497 continue;
498 }
499
500 // we get here if psduIt points to a psduMap containing QoS data frame(s); find (if any)
501 // the QoS data frame(s) addressed to EMLSR clients and add them to the appropriate
502 // frame exchange sequence
503 for (const auto& staIdPsduPair : psduIt->psduMap)
504 {
505 std::for_each_n(m_staMacs.cbegin(), m_nEmlsrStations, [&](auto&& staMac) {
506 if (!staMac->GetLinkIdByAddress(staIdPsduPair.second->GetAddr1()))
507 {
508 // not addressed to this non-AP MLD
509 return;
510 }
511 // a QoS data frame starts a new frame exchange sequence if there is no previous
512 // MU-RTS TF that has been sent on the same link and is not already followed by
513 // a QoS data frame
514 std::size_t id = staMac->GetDevice()->GetNode()->GetId() - 1;
515 for (auto& frameExchange : frameExchanges.at(id))
516 {
517 if (IsTrigger(frameExchange.front()->psduMap) &&
518 frameExchange.front()->linkId == psduIt->linkId &&
519 frameExchange.size() == 1)
520 {
521 auto it = std::next(frameExchange.front());
522 while (it != m_txPsdus.end())
523 {
524 // stop at the first frame other than CTS sent on this link
525 if (it->linkId == psduIt->linkId &&
526 !it->psduMap.begin()->second->GetHeader(0).IsCts())
527 {
528 break;
529 }
530 ++it;
531 }
532 if (it == psduIt)
533 {
534 // the QoS data frame actually followed the MU-RTS TF
535 frameExchange.emplace_back(psduIt);
536 return;
537 }
538 }
539 }
540 frameExchanges.at(id).emplace_back(FrameExchange{psduIt});
541 });
542 }
543 psduIt++;
544 }
545
546 /**
547 * Let's focus on the first two frame exchanges for each EMLSR clients. If all setup links are
548 * EMLSR links, both frame exchanges are protected by MU-RTS TF and occur one after another.
549 * Otherwise, one frame exchange occurs on the non-EMLSR link and is not protected by
550 * MU-RTS TF; the other frame exchange occurs on an EMLSR link and is protected by MU-RTS TF.
551 */
552 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
553 {
554 NS_TEST_EXPECT_MSG_GT_OR_EQ(frameExchanges.at(i).size(),
555 2,
556 "Expected at least 2 frame exchange sequences "
557 << "involving EMLSR client " << i);
558
559 auto firstExchangeIt = frameExchanges.at(i).begin();
560 auto secondExchangeIt = std::next(firstExchangeIt);
561
562 const auto firstAmpduTxEnd =
563 firstExchangeIt->back()->startTx +
565 firstExchangeIt->back()->psduMap,
566 firstExchangeIt->back()->txVector,
567 m_staMacs[i]->GetWifiPhy(firstExchangeIt->back()->linkId)->GetPhyBand());
568 const auto secondAmpduTxStart = secondExchangeIt->front()->startTx;
569
570 if (m_staMacs[i]->GetNLinks() == m_emlsrLinks.size())
571 {
572 // all links are EMLSR links
573 NS_TEST_EXPECT_MSG_EQ(IsTrigger(firstExchangeIt->front()->psduMap),
574 true,
575 "Expected an MU-RTS TF as ICF of first frame exchange sequence");
577 firstExchangeIt->back()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
578 true,
579 "Expected a QoS data frame in the first frame exchange sequence");
580
581 NS_TEST_EXPECT_MSG_EQ(IsTrigger(secondExchangeIt->front()->psduMap),
582 true,
583 "Expected an MU-RTS TF as ICF of second frame exchange sequence");
585 secondExchangeIt->back()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
586 true,
587 "Expected a QoS data frame in the second frame exchange sequence");
588
589 NS_TEST_EXPECT_MSG_LT(firstAmpduTxEnd,
590 secondAmpduTxStart,
591 "A-MPDUs are not sent one after another");
592 }
593 else
594 {
595 std::vector<uint8_t> nonEmlsrIds;
596 auto setupLinks = m_staMacs[i]->GetSetupLinkIds();
597 std::set_difference(setupLinks.begin(),
598 setupLinks.end(),
599 m_emlsrLinks.begin(),
600 m_emlsrLinks.end(),
601 std::back_inserter(nonEmlsrIds));
602 NS_TEST_ASSERT_MSG_EQ(nonEmlsrIds.size(), 1, "Unexpected number of non-EMLSR links");
603
604 auto nonEmlsrLinkExchangeIt = firstExchangeIt->front()->linkId == nonEmlsrIds[0]
605 ? firstExchangeIt
606 : secondExchangeIt;
607 NS_TEST_EXPECT_MSG_EQ(IsTrigger(nonEmlsrLinkExchangeIt->front()->psduMap),
608 false,
609 "Did not expect an MU-RTS TF as ICF on non-EMLSR link");
611 nonEmlsrLinkExchangeIt->front()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
612 true,
613 "Expected a QoS data frame on the non-EMLSR link");
614
615 auto emlsrLinkExchangeIt =
616 nonEmlsrLinkExchangeIt == firstExchangeIt ? secondExchangeIt : firstExchangeIt;
617 NS_TEST_EXPECT_MSG_NE(+emlsrLinkExchangeIt->front()->linkId,
618 +nonEmlsrIds[0],
619 "Expected this exchange not to occur on non-EMLSR link");
620 NS_TEST_EXPECT_MSG_EQ(IsTrigger(emlsrLinkExchangeIt->front()->psduMap),
621 true,
622 "Expected an MU-RTS TF as ICF on the EMLSR link");
624 emlsrLinkExchangeIt->back()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
625 true,
626 "Expected a QoS data frame on the EMLSR link");
627
628 NS_TEST_EXPECT_MSG_GT(firstAmpduTxEnd,
629 secondAmpduTxStart,
630 "A-MPDUs are not sent concurrently");
631 }
632
633 // we are done with processing the first two frame exchanges, remove them
634 frameExchanges.at(i).erase(firstExchangeIt);
635 frameExchanges.at(i).erase(secondExchangeIt);
636 }
637
638 /**
639 * A and B are two EMLSR clients. No ICF before the second QoS data frame because B
640 * has not switched to listening mode. ICF is sent before the third QoS data frame because
641 * A has switched to listening mode. C is a non-EMLSR client.
642 *
643 * ┌─────┐ A switches to listening
644 * │QoS x│ after transition delay
645 * │ to A│ |
646 * ┌───┐ ├─────┤ ┌─────┐
647 * │MU │ │QoS x│ │QoS y│
648 * [link 0] │RTS│ │ to B│ │ to B│
649 * ────────────┴───┴┬───┬┴─────┴┬──┬┴─────┴┬──┬────────────
650 * │CTS│ │BA│ │BA│
651 * ├───┤ ├──┤ └──┘
652 * │CTS│ │BA│
653 * └───┘ └──┘ AP continues the TXOP A switches to listening
654 * after PIFS recovery after transition delay
655 * │ │
656 * ┌─────┐ ┌───┐ ┌─────┐ │┌───┐ ┌───┐
657 * │QoS z│ │MU │ │QoS x│ ││MU │ ┌───┐ │CF-│
658 * [link 1] │ to C│ │RTS│ │ to A│ ││RTS│ │BAR│ │End│
659 * ───────────────────────────────┴─────┴┬──┬┴───┴┬───┬┴─────┴┬──┬┴───┴┬───┬┴───┴┬──┬┴───┴─
660 * │BA│ │CTS│ │BA│ │CTS│ │BA│
661 * └──┘ └───┘ └──x └───┘ └──┘
662 */
663 if (m_nEmlsrStations == 2 && m_apMac->GetNLinks() == m_emlsrLinks.size())
664 {
665 // the following checks are only done with 2 EMLSR clients having no non-EMLSR link
666 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
667 {
668 NS_TEST_EXPECT_MSG_GT_OR_EQ(frameExchanges.at(i).size(),
669 2,
670 "Expected at least 2 frame exchange sequences "
671 << "involving EMLSR client " << i);
672 // the first frame exchange must start with an ICF
673 auto firstExchangeIt = frameExchanges.at(i).begin();
674
675 NS_TEST_EXPECT_MSG_EQ(IsTrigger(firstExchangeIt->front()->psduMap),
676 true,
677 "Expected an MU-RTS TF as ICF of first frame exchange sequence");
679 firstExchangeIt->back()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
680 true,
681 "Expected a QoS data frame in the first frame exchange sequence");
682 }
683
684 // the second frame exchange is the one that starts first
685 auto secondExchangeIt = std::next(frameExchanges.at(0).begin())->front()->startTx <
686 std::next(frameExchanges.at(1).begin())->front()->startTx
687 ? std::next(frameExchanges.at(0).begin())
688 : std::next(frameExchanges.at(1).begin());
689 decltype(secondExchangeIt) thirdExchangeIt;
690 std::size_t thirdExchangeStaId;
691
692 if (secondExchangeIt == std::next(frameExchanges.at(0).begin()))
693 {
694 thirdExchangeIt = std::next(frameExchanges.at(1).begin());
695 thirdExchangeStaId = 1;
696 }
697 else
698 {
699 thirdExchangeIt = std::next(frameExchanges.at(0).begin());
700 thirdExchangeStaId = 0;
701 }
702
703 // the second frame exchange is not protected by the ICF and starts a SIFS after the end
704 // of the previous one
705 NS_TEST_EXPECT_MSG_EQ(IsTrigger(secondExchangeIt->front()->psduMap),
706 false,
707 "Expected no ICF for the second frame exchange sequence");
709 secondExchangeIt->front()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
710 true,
711 "Expected a QoS data frame in the second frame exchange sequence");
712
713 // the first two frame exchanges occur on the same link
714 NS_TEST_EXPECT_MSG_EQ(+secondExchangeIt->front()->linkId,
715 +frameExchanges.at(0).begin()->front()->linkId,
716 "Expected the first two frame exchanges to occur on the same link");
717
718 auto bAckRespIt = std::prev(secondExchangeIt->front());
719 NS_TEST_EXPECT_MSG_EQ(bAckRespIt->psduMap.cbegin()->second->GetHeader(0).IsBlockAck(),
720 true,
721 "Expected a BlockAck response before the second frame exchange");
722 auto bAckRespTxEnd =
723 bAckRespIt->startTx +
724 WifiPhy::CalculateTxDuration(bAckRespIt->psduMap,
725 bAckRespIt->txVector,
726 m_apMac->GetWifiPhy(bAckRespIt->linkId)->GetPhyBand());
727
728 // the second frame exchange starts a SIFS after the previous one
730 bAckRespTxEnd + m_apMac->GetWifiPhy(bAckRespIt->linkId)->GetSifs(),
731 secondExchangeIt->front()->startTx,
732 "Expected the second frame exchange to start a SIFS after the first one");
733
734 // the third frame exchange is protected by MU-RTS and occurs on a different link
735 NS_TEST_EXPECT_MSG_EQ(IsTrigger(thirdExchangeIt->front()->psduMap),
736 true,
737 "Expected an MU-RTS as ICF for the third frame exchange sequence");
739 thirdExchangeIt->back()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
740 true,
741 "Expected a QoS data frame in the third frame exchange sequence");
742
744 +secondExchangeIt->front()->linkId,
745 +thirdExchangeIt->front()->linkId,
746 "Expected the second and third frame exchanges to occur on distinct links");
747
748 auto secondQosIt = secondExchangeIt->front();
749 auto secondQosTxEnd =
750 secondQosIt->startTx +
751 WifiPhy::CalculateTxDuration(secondQosIt->psduMap,
752 secondQosIt->txVector,
753 m_apMac->GetWifiPhy(secondQosIt->linkId)->GetPhyBand());
754
755 NS_TEST_EXPECT_MSG_GT_OR_EQ(thirdExchangeIt->front()->startTx,
756 secondQosTxEnd + m_transitionDelay.at(thirdExchangeStaId),
757 "Transmission started before transition delay");
758
759 // the BlockAck of the third frame exchange is not received correctly, so there should be
760 // another frame exchange
761 NS_TEST_EXPECT_MSG_EQ((thirdExchangeIt != frameExchanges.at(thirdExchangeStaId).end()),
762 true,
763 "Expected a fourth frame exchange");
764 auto fourthExchangeIt = std::next(thirdExchangeIt);
765
766 // the fourth frame exchange is protected by MU-RTS
767 NS_TEST_EXPECT_MSG_EQ(IsTrigger(fourthExchangeIt->front()->psduMap),
768 true,
769 "Expected an MU-RTS as ICF for the fourth frame exchange sequence");
770
771 bAckRespIt = std::prev(fourthExchangeIt->front());
772 NS_TEST_EXPECT_MSG_EQ(bAckRespIt->psduMap.cbegin()->second->GetHeader(0).IsBlockAck(),
773 true,
774 "Expected a BlockAck response before the fourth frame exchange");
775 auto phy = m_apMac->GetWifiPhy(bAckRespIt->linkId);
776 bAckRespTxEnd = bAckRespIt->startTx + WifiPhy::CalculateTxDuration(bAckRespIt->psduMap,
777 bAckRespIt->txVector,
778 phy->GetPhyBand());
779
780 // the fourth frame exchange starts a PIFS after the previous one because the AP
781 // performs PIFS recovery (the initial frame in the TXOP was successfully received by
782 // a non-EMLSR client)
783 NS_TEST_EXPECT_MSG_GT_OR_EQ(fourthExchangeIt->front()->startTx,
784 bAckRespTxEnd + phy->GetPifs(),
785 "Transmission started less than a PIFS after BlockAck");
786 NS_TEST_EXPECT_MSG_LT(fourthExchangeIt->front()->startTx,
787 bAckRespTxEnd + phy->GetPifs() +
788 MicroSeconds(1) /* propagation delay upper bound */,
789 "Transmission started too much time after BlockAck");
790
791 auto bAckReqIt = std::next(fourthExchangeIt->front(), 2);
792 NS_TEST_EXPECT_MSG_EQ(bAckReqIt->psduMap.cbegin()->second->GetHeader(0).IsBlockAckReq(),
793 true,
794 "Expected a BlockAck request in the fourth frame exchange");
795
796 // we are done with processing the frame exchanges, remove them (two frame exchanges
797 // per EMLSR client, plus the last one)
798 frameExchanges.at(0).pop_front();
799 frameExchanges.at(0).pop_front();
800 frameExchanges.at(1).pop_front();
801 frameExchanges.at(1).pop_front();
802 frameExchanges.at(thirdExchangeStaId).pop_front();
803 }
804
805 /**
806 * After disabling EMLSR mode, no MU-RTS TF should be sent. After the exchange of
807 * EML Operating Mode Notification frames, a number of packets are generated at the AP MLD
808 * to prepare two A-MPDUs for each EMLSR client.
809 *
810 * EMLSR client with EMLSR mode to be enabled on all links (A is the EMLSR client, B is the
811 * non-EMLSR client):
812 *
813 * [link 0] | power save mode
814 * ────────────────────────────────────────────────────────
815 * ┌─────┬─────┐ ┌──────┬──────┐
816 * │QoS 8│QoS 9│ │QoS 10│QoS 11│
817 * │ to A│ to A│ │ to A │ to A │
818 * ┌───┐ ┌───┐ ├─────┼─────┤ ├──────┼──────┤
819 * ┌───┐ │MU │ │EML│ │QoS 8│QoS 9│ │QoS 10│QoS 11│
820 * [link 1] │ACK│ │RTS│ │OM │ │ to B│ to B│ │ to B │ to B │
821 * ────┬───┬┴───┴──┴───┴┬───┬┴───┴┬───┬──┴─────┴─────┴┬──┬────┴──────┴──────┴┬──┬─────
822 * │EML│ │CTS│ │ACK│ │BA│ │BA│
823 * │OM │ └───┘ └───┘ ├──┤ ├──┤
824 * └───┘ │BA│ │BA│
825 * └──┘ └──┘
826 *
827 * [link 2] | power save mode
828 * ────────────────────────────────────────────────────────────────────────────
829 *
830 *
831 * EMLSR client with EMLSR mode to be enabled on not all the links (A is the EMLSR client,
832 * B is the non-EMLSR client):
833 * ┌─────┬─────┐
834 * │QoS 8│QoS 9│
835 * │ to A│ to A│
836 * ├─────┼─────┤
837 * │QoS 8│QoS 9│
838 * [link 0 - non EMLSR] │ to B│ to B│
839 * ─────────────────────────────────────────┴─────┴─────┴┬──┬─────────────
840 * │BA│
841 * ├──┤
842 * │BA│
843 * └──┘
844 * ┌──────┬──────┐
845 * │QoS 10│QoS 11│
846 * │ to A │ to A │
847 * ┌───┐ ┌───┐ ├──────┼──────┤
848 * ┌───┐ │MU │ │EML│ │QoS 10│QoS 11│
849 * [link 1] │ACK│ │RTS│ │OM │ │ to B │ to B │
850 * ────┬───┬┴───┴──┴───┴┬───┬┴───┴┬───┬──┴──────┴──────┴┬──┬─────
851 * │EML│ │CTS│ │ACK│ │BA│
852 * │OM │ └───┘ └───┘ ├──┤
853 * └───┘ │BA│
854 * └──┘
855 *
856 * [link 2] | power save mode
857 * ────────────────────────────────────────────────────────────────────────────
858 *
859 */
860
861 // for each EMLSR client, there should be a frame exchange with ICF and no data frame
862 // (ICF protects the EML Notification response) if the EML Notification response is sent
863 // while EMLSR mode is still enabled and two frame exchanges with data frames
864 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
865 {
866 // the default EMLSR Manager requests to send EML Notification frames on the link where
867 // the main PHY is operating; if EMLSR mode is still enabled on this link when the AP MLD
868 // sends the EML Notification response, the latter is protected by an ICF
869 auto exchangeIt = frameExchanges.at(i).cbegin();
870
871 auto linkIdOpt = m_staMacs[i]->GetLinkForPhy(m_mainPhyId);
872 NS_TEST_ASSERT_MSG_EQ(linkIdOpt.has_value(),
873 true,
874 "Didn't find a link on which the main PHY is operating");
875
876 if (IsTrigger(exchangeIt->front()->psduMap))
877 {
878 NS_TEST_EXPECT_MSG_EQ(+exchangeIt->front()->linkId,
879 +linkIdOpt.value(),
880 "ICF was not sent on the expected link");
881 NS_TEST_EXPECT_MSG_EQ(exchangeIt->size(),
882 1,
883 "Expected no data frame in the first frame exchange sequence");
884 frameExchanges.at(i).pop_front();
885 }
886
887 NS_TEST_EXPECT_MSG_GT_OR_EQ(frameExchanges.at(i).size(),
888 2,
889 "Expected at least 2 frame exchange sequences "
890 << "involving EMLSR client " << i);
891
892 auto firstExchangeIt = frameExchanges.at(i).cbegin();
893 auto secondExchangeIt = std::next(firstExchangeIt);
894
895 const auto firstAmpduTxEnd =
896 firstExchangeIt->back()->startTx +
898 firstExchangeIt->back()->psduMap,
899 firstExchangeIt->back()->txVector,
900 m_staMacs[i]->GetWifiPhy(firstExchangeIt->back()->linkId)->GetPhyBand());
901 const auto secondAmpduTxStart = secondExchangeIt->front()->startTx;
902
904 firstExchangeIt->front()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
905 true,
906 "Expected a QoS data frame in the first frame exchange sequence");
907 NS_TEST_EXPECT_MSG_EQ(firstExchangeIt->size(),
908 1,
909 "Expected one frame only in the first frame exchange sequence");
910
912 secondExchangeIt->front()->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
913 true,
914 "Expected a QoS data frame in the second frame exchange sequence");
915 NS_TEST_EXPECT_MSG_EQ(secondExchangeIt->size(),
916 1,
917 "Expected one frame only in the second frame exchange sequence");
918
919 if (m_staMacs[i]->GetNLinks() == m_emlsrLinks.size())
920 {
921 // all links are EMLSR links: the two QoS data frames are sent one after another on
922 // the link used for sending EML OMN
924 +firstExchangeIt->front()->linkId,
925 +linkIdOpt.value(),
926 "First frame exchange expected to occur on link used to send EML OMN");
927
929 +secondExchangeIt->front()->linkId,
930 +linkIdOpt.value(),
931 "Second frame exchange expected to occur on link used to send EML OMN");
932
933 NS_TEST_EXPECT_MSG_LT(firstAmpduTxEnd,
934 secondAmpduTxStart,
935 "A-MPDUs are not sent one after another");
936 }
937 else
938 {
939 // the two QoS data frames are sent concurrently on distinct links
940 NS_TEST_EXPECT_MSG_NE(+firstExchangeIt->front()->linkId,
941 +secondExchangeIt->front()->linkId,
942 "Frame exchanges expected to occur on distinct links");
943
944 NS_TEST_EXPECT_MSG_GT(firstAmpduTxEnd,
945 secondAmpduTxStart,
946 "A-MPDUs are not sent concurrently");
947 }
948 }
949}
950
951void
953{
954 std::optional<std::size_t> staId;
955 for (std::size_t id = 0; id < m_nEmlsrStations + m_nNonEmlsrStations; id++)
956 {
957 if (m_staMacs.at(id)->GetLinkIdByAddress(address))
958 {
959 staId = id;
960 break;
961 }
962 }
963 NS_TEST_ASSERT_MSG_EQ(staId.has_value(), true, "Not an address of a non-AP MLD " << address);
964
965 // check that all EMLSR links (but the link used for ML setup) of the EMLSR clients
966 // are considered to be in power save mode by the AP MLD; all the other links have
967 // transitioned to active mode instead
968 for (uint8_t linkId = 0; linkId < m_apMac->GetNLinks(); linkId++)
969 {
970 bool psModeExpected =
971 *staId < m_nEmlsrStations && linkId != m_mainPhyId && m_emlsrLinks.count(linkId) == 1;
972 auto addr = m_staMacs.at(*staId)->GetAddress();
973 auto psMode = m_apMac->GetWifiRemoteStationManager(linkId)->IsInPsMode(addr);
975 psModeExpected,
976 "EMLSR link " << +linkId << " of EMLSR client " << *staId
977 << " not in " << (psModeExpected ? "PS" : "active")
978 << " mode");
979 // check that AP is blocking transmission of QoS data frames on this link
981 addr,
982 linkId,
983 WifiQueueBlockedReason::POWER_SAVE_MODE,
984 psModeExpected,
985 "Checking PM mode after association on AP MLD for EMLSR client " +
986 std::to_string(*staId),
987 false);
988 }
989}
990
991void
993 const WifiTxVector& txVector,
994 uint8_t linkId)
995{
996 // the AP is replying to a received EMLSR Notification frame
997 auto pkt = mpdu->GetPacket()->Copy();
998 const auto& hdr = mpdu->GetHeader();
1000 MgtEmlOmn frame;
1001 pkt->RemoveHeader(frame);
1002
1003 std::optional<std::size_t> staId;
1004 for (std::size_t id = 0; id < m_nEmlsrStations; id++)
1005 {
1006 if (m_staMacs.at(id)->GetFrameExchangeManager(linkId)->GetAddress() == hdr.GetAddr1())
1007 {
1008 staId = id;
1009 break;
1010 }
1011 }
1012 NS_TEST_ASSERT_MSG_EQ(staId.has_value(),
1013 true,
1014 "Not an address of an EMLSR client " << hdr.GetAddr1());
1015
1016 // The EMLSR mode change occurs a Transition Timeout after the end of the PPDU carrying the Ack
1017 auto phy = m_apMac->GetWifiPhy(linkId);
1018 auto txDuration =
1019 WifiPhy::CalculateTxDuration(mpdu->GetSize() + 4, // A-MPDU Subframe header size
1020 txVector,
1021 phy->GetPhyBand());
1022 WifiTxVector ackTxVector =
1023 m_staMacs.at(*staId)->GetWifiRemoteStationManager(linkId)->GetAckTxVector(hdr.GetAddr2(),
1024 txVector);
1025 auto ackDuration = WifiPhy::CalculateTxDuration(GetAckSize() + 4, // A-MPDU Subframe header
1026 ackTxVector,
1027 phy->GetPhyBand());
1028
1029 Simulator::Schedule(txDuration + phy->GetSifs() + ackDuration, [=, this]() {
1030 if (frame.m_emlControl.emlsrMode == 1)
1031 {
1032 // EMLSR mode enabled. Check that all EMLSR links of the EMLSR clients are considered
1033 // to be in active mode by the AP MLD
1034 for (const auto linkId : m_emlsrLinks)
1035 {
1036 auto addr = m_staMacs.at(*staId)->GetAddress();
1037 auto psMode = m_apMac->GetWifiRemoteStationManager(linkId)->IsInPsMode(addr);
1038 NS_TEST_EXPECT_MSG_EQ(psMode,
1039 false,
1040 "EMLSR link " << +linkId << " of EMLSR client " << *staId
1041 << " not in active mode");
1042 // check that AP is not blocking transmission of QoS data frames on this link
1043 CheckBlockedLink(
1044 m_apMac,
1045 addr,
1046 linkId,
1047 WifiQueueBlockedReason::POWER_SAVE_MODE,
1048 false,
1049 "Checking EMLSR links on AP MLD after EMLSR mode is enabled on EMLSR client " +
1050 std::to_string(*staId),
1051 false);
1052 }
1053 }
1054 else
1055 {
1056 // EMLSR mode disabled. Check that all EMLSR links (but the link used to send the
1057 // EML Notification frame) of the EMLSR clients are considered to be in power save
1058 // mode by the AP MLD; the other links are in active mode
1059 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1060 {
1061 bool psModeExpected = id != linkId && m_emlsrLinks.count(id) == 1;
1062 auto addr = m_staMacs.at(*staId)->GetAddress();
1063 auto psMode = m_apMac->GetWifiRemoteStationManager(id)->IsInPsMode(addr);
1064 NS_TEST_EXPECT_MSG_EQ(psMode,
1065 psModeExpected,
1066 "EMLSR link "
1067 << +id << " of EMLSR client " << *staId << " not in "
1068 << (psModeExpected ? "PS" : "active") << " mode");
1069 // check that AP is blocking transmission of QoS data frames on this link
1070 CheckBlockedLink(
1071 m_apMac,
1072 addr,
1073 id,
1074 WifiQueueBlockedReason::POWER_SAVE_MODE,
1075 psModeExpected,
1076 "Checking links on AP MLD after EMLSR mode is disabled on EMLSR client " +
1077 std::to_string(*staId),
1078 false);
1079 }
1080 }
1081 });
1082}
1083
1084void
1086 const WifiTxVector& txVector,
1087 uint8_t linkId)
1088{
1089 // an EMLSR client is sending an EMLSR Notification frame
1090 auto pkt = mpdu->GetPacket()->Copy();
1091 const auto& hdr = mpdu->GetHeader();
1093 MgtEmlOmn frame;
1094 pkt->RemoveHeader(frame);
1095
1096 std::optional<std::size_t> staId;
1097 for (std::size_t id = 0; id < m_nEmlsrStations; id++)
1098 {
1099 if (m_staMacs.at(id)->GetFrameExchangeManager(linkId)->GetAddress() == hdr.GetAddr2())
1100 {
1101 staId = id;
1102 break;
1103 }
1104 }
1105 NS_TEST_ASSERT_MSG_EQ(staId.has_value(),
1106 true,
1107 "Not an address of an EMLSR client " << hdr.GetAddr1());
1108
1109 auto phy = m_staMacs.at(*staId)->GetWifiPhy(linkId);
1110 auto txDuration = WifiPhy::CalculateTxDuration(mpdu->GetSize(), txVector, phy->GetPhyBand());
1111 auto ackTxVector =
1112 m_apMac->GetWifiRemoteStationManager(linkId)->GetAckTxVector(hdr.GetAddr2(), txVector);
1113 auto ackDuration = WifiPhy::CalculateTxDuration(GetAckSize(), ackTxVector, phy->GetPhyBand());
1114 auto cfEndDuration = WifiPhy::CalculateTxDuration(
1116 m_staMacs.at(*staId)->GetWifiRemoteStationManager(linkId)->GetRtsTxVector(
1118 txVector.GetChannelWidth()),
1119 phy->GetPhyBand());
1120
1121 if (frame.m_emlControl.emlsrMode != 0)
1122 {
1123 return;
1124 }
1125
1126 // EMLSR mode disabled
1127 auto timeToCfEnd = txDuration + phy->GetSifs() + ackDuration + phy->GetSifs() + cfEndDuration;
1128
1129 // before the end of the CF-End frame, this link only is not blocked on both the
1130 // EMLSR client and the AP MLD
1131 Simulator::Schedule(timeToCfEnd - MicroSeconds(1), [=, this]() {
1132 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1133 {
1134 CheckBlockedLink(m_staMacs.at(*staId),
1135 m_apMac->GetAddress(),
1136 id,
1137 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1138 id != linkId && m_staMacs.at(*staId)->IsEmlsrLink(id),
1139 "Checking links on EMLSR client " + std::to_string(*staId) +
1140 " before the end of CF-End frame");
1142 m_staMacs.at(*staId)->GetAddress(),
1143 id,
1144 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1145 id != linkId && m_staMacs.at(*staId)->IsEmlsrLink(id),
1146 "Checking links of EMLSR client " + std::to_string(*staId) +
1147 " on the AP MLD before the end of CF-End frame");
1148 }
1149 });
1150 // after the end of the CF-End frame, all links for the EMLSR client are blocked on the
1151 // AP MLD
1152 Simulator::Schedule(timeToCfEnd + MicroSeconds(1), [=, this]() {
1153 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1154 {
1155 if (m_staMacs.at(*staId)->IsEmlsrLink(id))
1156 {
1157 CheckBlockedLink(
1158 m_apMac,
1159 m_staMacs.at(*staId)->GetAddress(),
1160 id && m_staMacs.at(*staId)->IsEmlsrLink(id),
1161 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
1162 true,
1163 "Checking links of EMLSR client " + std::to_string(*staId) +
1164 " are all blocked on the AP MLD right after the end of CF-End");
1165 }
1166 }
1167 });
1168 // before the end of the transition delay, all links for the EMLSR client are still
1169 // blocked on the AP MLD
1170 Simulator::Schedule(timeToCfEnd + m_transitionDelay.at(*staId) - MicroSeconds(1), [=, this]() {
1171 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1172 {
1173 if (m_staMacs.at(*staId)->IsEmlsrLink(id))
1174 {
1175 CheckBlockedLink(m_apMac,
1176 m_staMacs.at(*staId)->GetAddress(),
1177 id,
1178 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
1179 true,
1180 "Checking links of EMLSR client " + std::to_string(*staId) +
1181 " are all blocked on the AP MLD before the end of "
1182 "transition delay");
1183 }
1184 }
1185 });
1186 // immediately after the transition delay, all links for the EMLSR client are unblocked
1187 Simulator::Schedule(timeToCfEnd + m_transitionDelay.at(*staId) + MicroSeconds(1), [=, this]() {
1188 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1189 {
1190 if (m_staMacs.at(*staId)->IsEmlsrLink(id))
1191 {
1192 CheckBlockedLink(m_apMac,
1193 m_staMacs.at(*staId)->GetAddress(),
1194 id,
1195 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
1196 false,
1197 "Checking links of EMLSR client " + std::to_string(*staId) +
1198 " are all unblocked on the AP MLD after the transition delay");
1199 }
1200 }
1201 });
1202}
1203
1204void
1206 const WifiTxVector& txVector,
1207 uint8_t linkId)
1208{
1209 CtrlTriggerHeader trigger;
1210 mpdu->GetPacket()->PeekHeader(trigger);
1211 if (!trigger.IsMuRts())
1212 {
1213 return;
1214 }
1215
1217 true,
1218 "Did not expect an ICF before enabling EMLSR mode");
1219
1222 "Unexpected preamble type for the Initial Control frame");
1223 auto rate = txVector.GetMode().GetDataRate(txVector);
1224 NS_TEST_EXPECT_MSG_EQ((rate == 6e6 || rate == 12e6 || rate == 24e6),
1225 true,
1226 "Unexpected rate for the Initial Control frame: " << rate);
1227
1228 bool found = false;
1229 Time maxPaddingDelay{};
1230
1231 for (const auto& userInfo : trigger)
1232 {
1233 auto addr = m_apMac->GetMldOrLinkAddressByAid(userInfo.GetAid12());
1234 NS_TEST_ASSERT_MSG_EQ(addr.has_value(),
1235 true,
1236 "AID " << userInfo.GetAid12() << " not found");
1237
1238 if (m_apMac->GetWifiRemoteStationManager(linkId)->GetEmlsrEnabled(*addr))
1239 {
1240 found = true;
1241
1242 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
1243 {
1244 if (m_staMacs.at(i)->GetAddress() == *addr)
1245 {
1246 maxPaddingDelay = Max(maxPaddingDelay, m_paddingDelay.at(i));
1247 break;
1248 }
1249 }
1250
1251 // check that the AP has blocked transmission on all other EMLSR links
1252 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1253 {
1254 if (!m_apMac->GetWifiRemoteStationManager(id)->GetEmlsrEnabled(*addr))
1255 {
1256 continue;
1257 }
1258
1260 *addr,
1261 id,
1262 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1263 id != linkId,
1264 "Checking that AP blocked transmissions on all other EMLSR "
1265 "links after sending ICF to client with AID=" +
1266 std::to_string(userInfo.GetAid12()),
1267 false);
1268 }
1269 }
1270 }
1271
1272 NS_TEST_EXPECT_MSG_EQ(found, true, "Expected ICF to be addressed to at least an EMLSR client");
1273
1274 auto txDuration = WifiPhy::CalculateTxDuration(mpdu->GetSize(),
1275 txVector,
1276 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
1277
1278 if (maxPaddingDelay.IsStrictlyPositive())
1279 {
1280 // compare the TX duration of this Trigger Frame to that of the Trigger Frame with no
1281 // padding added
1282 trigger.SetPaddingSize(0);
1283 auto pkt = Create<Packet>();
1284 pkt->AddHeader(trigger);
1285 auto txDurationWithout =
1286 WifiPhy::CalculateTxDuration(Create<WifiPsdu>(pkt, mpdu->GetHeader()),
1287 txVector,
1288 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
1289
1290 NS_TEST_EXPECT_MSG_EQ(txDuration,
1291 txDurationWithout + maxPaddingDelay,
1292 "Unexpected TX duration of the MU-RTS TF with padding "
1293 << maxPaddingDelay.As(Time::US));
1294 }
1295
1296 // check that the EMLSR clients have blocked transmissions on other links, switched their main
1297 // PHY (if needed) and have put aux PHYs to sleep after receiving this ICF
1298 for (const auto& userInfo : trigger)
1299 {
1300 for (std::size_t i = 0; i < m_nEmlsrStations; i++)
1301 {
1302 if (m_staMacs[i]->GetAssociationId() != userInfo.GetAid12())
1303 {
1304 continue;
1305 }
1306
1307 const auto mainPhyLinkId = m_staMacs[i]->GetLinkForPhy(m_mainPhyId);
1308
1309 Simulator::Schedule(txDuration + NanoSeconds(5), [=, this]() {
1310 for (uint8_t id = 0; id < m_staMacs[i]->GetNLinks(); id++)
1311 {
1312 // non-EMLSR links or links on which ICF is received are not blocked
1314 m_apMac->GetAddress(),
1315 id,
1316 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1317 id != linkId && m_staMacs[i]->IsEmlsrLink(id),
1318 "Checking EMLSR links on EMLSR client " + std::to_string(i) +
1319 " after receiving ICF");
1320 }
1321
1322 if (mainPhyLinkId != linkId)
1323 {
1324 CheckMainPhyTraceInfo(i, "DlTxopIcfReceivedByAuxPhy", mainPhyLinkId, linkId);
1325 }
1326
1328 });
1329
1330 break;
1331 }
1332 }
1333}
1334
1335void
1337 const WifiTxVector& txVector,
1338 uint8_t linkId)
1339{
1340 if (m_nEmlsrStations != 2 || m_apMac->GetNLinks() != m_emlsrLinks.size() ||
1342
1343 {
1344 // we are interested in frames sent to test transition delay
1345 return;
1346 }
1347
1348 std::size_t firstClientId = 0;
1349 std::size_t secondClientId = 1;
1350 auto addr = m_staMacs[secondClientId]->GetAddress();
1351 auto txDuration =
1352 WifiPhy::CalculateTxDuration(psduMap, txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand());
1353
1355
1356 switch (m_countQoSframes)
1357 {
1358 case 1:
1359 // generate another small packet addressed to the first EMLSR client only
1360 m_apMac->GetDevice()->GetNode()->AddApplication(
1361 GetApplication(DOWNLINK, firstClientId, 1, 40));
1362 // both EMLSR clients are about to receive a QoS data frame
1363 for (std::size_t clientId : {firstClientId, secondClientId})
1364 {
1365 Simulator::Schedule(txDuration, [=, this]() {
1366 for (uint8_t id = 0; id < m_staMacs[clientId]->GetNLinks(); id++)
1367 {
1368 // link on which QoS data is received is not blocked
1369 CheckBlockedLink(m_staMacs[clientId],
1370 m_apMac->GetAddress(),
1371 id,
1372 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1373 id != linkId,
1374 "Checking EMLSR links on EMLSR client " +
1375 std::to_string(clientId) +
1376 " after receiving the first QoS data frame");
1377 }
1378 });
1379 }
1380 break;
1381 case 2:
1382 // generate another small packet addressed to the second EMLSR client
1383 m_apMac->GetDevice()->GetNode()->AddApplication(
1384 GetApplication(DOWNLINK, secondClientId, 1, 40));
1385
1386 // when the transmission of the second QoS data frame starts, both EMLSR clients are
1387 // still blocking all the links but the one used to receive the QoS data frame
1388 for (std::size_t clientId : {firstClientId, secondClientId})
1389 {
1390 for (uint8_t id = 0; id < m_staMacs[clientId]->GetNLinks(); id++)
1391 {
1392 // link on which QoS data is received is not blocked
1393 CheckBlockedLink(m_staMacs[clientId],
1394 m_apMac->GetAddress(),
1395 id,
1396 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1397 id != linkId,
1398 "Checking EMLSR links on EMLSR client " +
1399 std::to_string(clientId) +
1400 " when starting the reception of the second QoS frame");
1401 }
1402 }
1403
1404 // the EMLSR client that is not the recipient of the QoS frame being transmitted will
1405 // switch back to listening mode after a transition delay starting from the end of
1406 // the PPDU carrying this QoS data frame
1407
1408 // immediately before the end of the PPDU, this link only is not blocked for the EMLSR
1409 // client on the AP MLD
1410 Simulator::Schedule(txDuration - NanoSeconds(1), [=, this]() {
1411 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1412 {
1414 addr,
1415 id,
1416 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1417 id != linkId,
1418 "Checking that links of EMLSR client " +
1419 std::to_string(secondClientId) +
1420 " are blocked on the AP MLD before the end of the PPDU");
1421 }
1422 });
1423 // immediately before the end of the PPDU, all the links on the EMLSR client that is not
1424 // the recipient of the second QoS frame are unblocked (they are unblocked when the
1425 // PHY-RXSTART.indication is not received)
1426 Simulator::Schedule(txDuration - NanoSeconds(1), [=, this]() {
1427 for (uint8_t id = 0; id < m_staMacs[secondClientId]->GetNLinks(); id++)
1428 {
1429 CheckBlockedLink(m_staMacs[secondClientId],
1430 m_apMac->GetAddress(),
1431 id,
1432 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1433 false,
1434 "Checking that links of EMLSR client " +
1435 std::to_string(secondClientId) +
1436 " are unblocked before the end of the second QoS frame");
1437 }
1438 });
1439 // immediately after the end of the PPDU, all links are blocked for the EMLSR client
1440 Simulator::Schedule(txDuration + NanoSeconds(1), [=, this]() {
1441 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1442 {
1444 addr,
1445 id,
1446 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
1447 true,
1448 "Checking links of EMLSR client " +
1449 std::to_string(secondClientId) +
1450 " are all blocked on the AP MLD after the end of the PPDU");
1451 }
1452 });
1453 // immediately before the transition delay, all links are still blocked for the EMLSR client
1455 txDuration + m_transitionDelay.at(secondClientId) - NanoSeconds(1),
1456 [=, this]() {
1457 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1458 {
1460 m_apMac,
1461 addr,
1462 id,
1463 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
1464 true,
1465 "Checking links of EMLSR client " + std::to_string(secondClientId) +
1466 " are all blocked on the AP MLD before the transition delay",
1467 false);
1468 }
1469 });
1470
1471 // 100 us before the transition delay expires, generate another small packet addressed
1472 // to a non-EMLSR client. The AP will start a TXOP to transmit this frame, while the
1473 // frame addressed to the EMLSR client is still queued because the transition delay has
1474 // not yet elapsed. The transition delay will expire while the AP is transmitting the
1475 // frame to the non-EMLSR client, so that the AP continues the TXOP to transmit the frame
1476 // to the EMLSR client and we can check that the AP performs PIFS recovery after missing
1477 // the BlockAck from the EMLSR client
1478 Simulator::Schedule(txDuration + m_transitionDelay.at(secondClientId) - MicroSeconds(100),
1479 [=, this]() {
1480 m_apMac->GetDevice()->GetNode()->AddApplication(
1482 });
1483
1484 break;
1485 case 3:
1486 // this is the frame addressed to a non-EMLSR client, which is transmitted before the
1487 // frame addressed to the EMLSR client, because the links of the latter are still blocked
1488 // at the AP because the transition delay has not yet elapsed
1490 psduMap.cbegin()->second->GetAddr1(),
1491 m_staMacs[m_nEmlsrStations]->GetFrameExchangeManager(linkId)->GetAddress(),
1492 "QoS frame not addressed to a non-EMLSR client");
1493
1494 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1495 {
1497 addr,
1498 id,
1499 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
1500 true,
1501 "Checking links of EMLSR client " + std::to_string(secondClientId) +
1502 " are all blocked on the AP MLD before the transition delay");
1503 }
1504 // Block transmissions to the EMLSR client on all the links but the one on which this
1505 // frame is sent, so that the AP will continue this TXOP to send the queued frame to the
1506 // EMLSR client once the transition delay elapses
1507 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1508 {
1509 if (id != linkId)
1510 {
1511 m_apMac->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED, addr, {id});
1512 }
1513 }
1514 break;
1515 case 4:
1516 // the AP is continuing the TXOP, no need to block transmissions anymore
1517 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1518 {
1519 m_apMac->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED, addr, {id});
1520 }
1521 // at the end of the fourth QoS frame, this link only is not blocked on the EMLSR
1522 // client receiving the frame
1523 Simulator::Schedule(txDuration, [=, this]() {
1524 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1525 {
1526 CheckBlockedLink(m_staMacs[secondClientId],
1527 m_apMac->GetAddress(),
1528 id,
1529 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1530 id != linkId,
1531 "Checking EMLSR links on EMLSR client " +
1532 std::to_string(secondClientId) +
1533 " after receiving the fourth QoS data frame");
1534 }
1535 });
1536 break;
1537 }
1538}
1539
1540void
1542 const WifiTxVector& txVector,
1543 uint8_t phyId)
1544{
1545 if (m_nEmlsrStations != 2 || m_apMac->GetNLinks() != m_emlsrLinks.size() ||
1547 {
1548 // we are interested in frames sent to test transition delay
1549 return;
1550 }
1551
1552 if (++m_countBlockAck == 4)
1553 {
1554 // fourth BlockAck is sent by a non-EMLSR client
1555 return;
1556 }
1557
1558 auto taddr = psduMap.cbegin()->second->GetAddr2();
1559 std::size_t clientId;
1560 if (m_staMacs[0]->GetLinkIdByAddress(taddr))
1561 {
1562 clientId = 0;
1563 }
1564 else
1565 {
1566 NS_TEST_ASSERT_MSG_EQ(m_staMacs[1]->GetLinkIdByAddress(taddr).has_value(),
1567 true,
1568 "Unexpected TA for BlockAck: " << taddr);
1569 clientId = 1;
1570 }
1571
1572 // find the link on which the main PHY is operating
1573 auto currMainPhyLinkId = m_staMacs[clientId]->GetLinkForPhy(phyId);
1575 currMainPhyLinkId.has_value(),
1576 true,
1577 "Didn't find the link on which the PHY sending the BlockAck is operating");
1578 auto linkId = *currMainPhyLinkId;
1579
1580 // we need the MLD address to check the status of the container queues
1581 auto addr = m_apMac->GetWifiRemoteStationManager(linkId)->GetMldAddress(taddr);
1582 NS_TEST_ASSERT_MSG_EQ(addr.has_value(), true, "MLD address not found for " << taddr);
1583
1584 auto apPhy = m_apMac->GetWifiPhy(linkId);
1585 auto txDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, apPhy->GetPhyBand());
1586 auto cfEndTxDuration = WifiPhy::CalculateTxDuration(
1588 m_apMac->GetWifiRemoteStationManager(linkId)->GetRtsTxVector(Mac48Address::GetBroadcast(),
1589 txVector.GetChannelWidth()),
1590 apPhy->GetPhyBand());
1591
1592 switch (m_countBlockAck)
1593 {
1594 case 5:
1595 // the PPDU carrying this BlockAck is corrupted, hence the AP MLD MAC receives the
1596 // PHY-RXSTART indication but it does not receive any frame from the PHY. Therefore,
1597 // at the end of the PPDU transmission, the AP MLD realizes that the EMLSR client has
1598 // not responded and makes an attempt at continuing the TXOP
1599
1600 // at the end of the PPDU, this link only is not blocked on both the EMLSR client and
1601 // the AP MLD
1602 Simulator::Schedule(txDuration, [=, this]() {
1603 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1604 {
1605 CheckBlockedLink(m_staMacs[clientId],
1606 m_apMac->GetAddress(),
1607 id,
1608 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1609 id != linkId,
1610 "Checking links on EMLSR client " + std::to_string(clientId) +
1611 " at the end of fourth BlockAck");
1613 *addr,
1614 id,
1615 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1616 id != linkId,
1617 "Checking links of EMLSR client " + std::to_string(clientId) +
1618 " on the AP MLD at the end of fourth BlockAck");
1619 }
1620 });
1621 // a SIFS after the end of the PPDU, still this link only is not blocked on both the
1622 // EMLSR client and the AP MLD
1623 Simulator::Schedule(txDuration + apPhy->GetSifs(), [=, this]() {
1624 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1625 {
1626 CheckBlockedLink(m_staMacs[clientId],
1627 m_apMac->GetAddress(),
1628 id,
1629 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1630 id != linkId,
1631 "Checking links on EMLSR client " + std::to_string(clientId) +
1632 " a SIFS after the end of fourth BlockAck");
1633 CheckBlockedLink(m_apMac,
1634 *addr,
1635 id,
1636 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1637 id != linkId,
1638 "Checking links of EMLSR client " + std::to_string(clientId) +
1639 " a SIFS after the end of fourth BlockAck");
1640 }
1641 });
1642 // corrupt this BlockAck so that the AP MLD sends a BlockAckReq later on
1643 {
1644 auto uid = psduMap.cbegin()->second->GetPacket()->GetUid();
1645 m_errorModel->SetList({uid});
1646 }
1647 break;
1648 case 6:
1649 // at the end of the PPDU, this link only is not blocked on both the EMLSR client and
1650 // the AP MLD
1651 Simulator::Schedule(txDuration, [=, this]() {
1652 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1653 {
1654 CheckBlockedLink(m_staMacs[clientId],
1655 m_apMac->GetAddress(),
1656 id,
1657 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1658 id != linkId,
1659 "Checking links on EMLSR client " + std::to_string(clientId) +
1660 " at the end of fifth BlockAck");
1662 *addr,
1663 id,
1664 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1665 id != linkId,
1666 "Checking links of EMLSR client " + std::to_string(clientId) +
1667 " on the AP MLD at the end of fifth BlockAck");
1668 }
1669 });
1670 // before the end of the CF-End frame, still this link only is not blocked on both the
1671 // EMLSR client and the AP MLD
1673 txDuration + apPhy->GetSifs() + cfEndTxDuration - MicroSeconds(1),
1674 [=, this]() {
1675 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1676 {
1677 CheckBlockedLink(m_staMacs[clientId],
1678 m_apMac->GetAddress(),
1679 id,
1680 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1681 id != linkId,
1682 "Checking links on EMLSR client " + std::to_string(clientId) +
1683 " before the end of CF-End frame");
1685 *addr,
1686 id,
1687 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
1688 id != linkId,
1689 "Checking links of EMLSR client " + std::to_string(clientId) +
1690 " on the AP MLD before the end of CF-End frame");
1691 }
1692 });
1693 // after the end of the CF-End frame, all links for the EMLSR client are blocked on the
1694 // AP MLD
1696 txDuration + apPhy->GetSifs() + cfEndTxDuration + MicroSeconds(1),
1697 [=, this]() {
1698 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1699 {
1701 m_apMac,
1702 *addr,
1703 id,
1704 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
1705 true,
1706 "Checking links of EMLSR client " + std::to_string(clientId) +
1707 " are all blocked on the AP MLD right after the end of CF-End");
1708 }
1709 });
1710 // before the end of the transition delay, all links for the EMLSR client are still
1711 // blocked on the AP MLD
1713 txDuration + apPhy->GetSifs() + cfEndTxDuration + m_transitionDelay.at(clientId) -
1714 MicroSeconds(1),
1715 [=, this]() {
1716 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1717 {
1719 m_apMac,
1720 *addr,
1721 id,
1722 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
1723 true,
1724 "Checking links of EMLSR client " + std::to_string(clientId) +
1725 " are all blocked on the AP MLD before the end of transition delay");
1726 }
1727 });
1728 // immediately after the transition delay, all links for the EMLSR client are unblocked
1730 txDuration + apPhy->GetSifs() + cfEndTxDuration + m_transitionDelay.at(clientId) +
1731 MicroSeconds(1),
1732 [=, this]() {
1733 for (uint8_t id = 0; id < m_apMac->GetNLinks(); id++)
1734 {
1736 m_apMac,
1737 *addr,
1738 id,
1739 WifiQueueBlockedReason::WAITING_EMLSR_TRANSITION_DELAY,
1740 false,
1741 "Checking links of EMLSR client " + std::to_string(clientId) +
1742 " are all unblocked on the AP MLD after the transition delay");
1743 }
1744 });
1745 break;
1746 }
1747}
1748
1749void
1759
1761 : EmlsrOperationsTestBase("Check EML UL TXOP transmissions (genBackoffIfTxopWithoutTx=" +
1762 std::to_string(params.genBackoffIfTxopWithoutTx)),
1763 m_emlsrLinks(params.linksToEnableEmlsrOn),
1764 m_channelWidth(params.channelWidth),
1765 m_auxPhyChannelWidth(params.auxPhyChannelWidth),
1766 m_mediumSyncDuration(params.mediumSyncDuration),
1767 m_msdMaxNTxops(params.msdMaxNTxops),
1768 m_emlsrEnabledTime(0),
1769 m_firstUlPktsGenTime(0),
1770 m_unblockMainPhyLinkDelay(MilliSeconds(20)),
1771 m_checkBackoffStarted(false),
1772 m_countQoSframes(0),
1773 m_countBlockAck(0),
1774 m_countRtsframes(0),
1775 m_genBackoffIfTxopWithoutTx(params.genBackoffIfTxopWithoutTx)
1776{
1777 m_nEmlsrStations = 1;
1779 m_linksToEnableEmlsrOn = params.linksToEnableEmlsrOn;
1780 m_mainPhyId = 1;
1781
1782 // when aux PHYs do not switch link, the main PHY switches back to its previous link after
1783 // a TXOP, hence the transition delay must exceed the channel switch delay (default: 250us)
1785 m_establishBaDl = {0};
1786 m_establishBaUl = {0};
1787 m_putAuxPhyToSleep = params.putAuxPhyToSleep;
1788 m_duration = Seconds(1);
1789
1790 NS_ABORT_MSG_IF(params.linksToEnableEmlsrOn.size() < 2,
1791 "This test requires at least two links to be configured as EMLSR links");
1792 for (uint8_t id = 0; id < 3; id++)
1793 {
1794 if (!m_emlsrLinks.contains(id))
1795 {
1796 // non-EMLSR link found
1797 m_nonEmlsrLink = id;
1798 break;
1799 }
1800 }
1801}
1802
1803void
1805{
1806 Config::SetDefault("ns3::EmlsrManager::AuxPhyChannelWidth",
1808 Config::SetDefault("ns3::DefaultEmlsrManager::SwitchAuxPhy", BooleanValue(false));
1809 // switch main PHY back delay should be at least a PIFS for the switch to occur
1810 Config::SetDefault("ns3::EhtConfiguration::MediumSyncDuration",
1812 Config::SetDefault("ns3::EhtConfiguration::MsdMaxNTxops", UintegerValue(m_msdMaxNTxops));
1813 Config::SetDefault("ns3::ChannelAccessManager::GenerateBackoffIfTxopWithoutTx",
1815 // Channel switch delay should be less than RTS TX time + SIFS + CTS TX time, otherwise
1816 // UL TXOPs cannot be initiated by aux PHYs
1817 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(MicroSeconds(75)));
1818 Config::SetDefault("ns3::WifiPhy::NotifyMacHdrRxEnd", BooleanValue(true));
1819
1821
1822 m_staMacs[0]->GetQosTxop(AC_BE)->TraceConnectWithoutContext(
1823 "BackoffTrace",
1825
1826 uint8_t linkId = 0;
1827 // configure channels of the given width
1829 {
1830 MHz_u bw{20};
1831 uint8_t number = band == WIFI_PHY_BAND_5GHZ ? 36 : 1;
1832
1833 auto width =
1834 std::min(m_channelWidth, band == WIFI_PHY_BAND_2_4GHZ ? MHz_u{40} : MHz_u{160});
1835 while (bw < width)
1836 {
1837 bw *= 2;
1838 number += Count20MHzSubchannels(bw);
1839 }
1840
1841 for (auto mac : std::initializer_list<Ptr<WifiMac>>{m_apMac, m_staMacs[0]})
1842 {
1843 mac->GetWifiPhy(linkId)->SetOperatingChannel(
1844 WifiPhy::ChannelTuple{number, width, band, 0});
1845 }
1846 linkId++;
1847 }
1848
1849 // install post reception error model on the AP affiliated with the AP MLD and operating on
1850 // the same link as the main PHY of the EMLSR client
1852 m_apMac->GetWifiPhy(m_mainPhyId)->SetPostReceptionErrorModel(m_errorModel);
1853}
1854
1855void
1857{
1858 NS_LOG_INFO("Backoff value " << backoff << " generated by EMLSR client on link " << +linkId
1859 << "\n");
1860 if (linkId != m_mainPhyId)
1861 {
1862 return; // we are only interested in backoff on main PHY link
1863 }
1864
1865 if (m_backoffEndTime)
1866 {
1868 {
1869 // another backoff value while checkBackoffStarted is true is generated only if
1870 // GenerateBackoffIfTxopWithoutTx is true
1873 true,
1874 "Another backoff value should not be generated while the main PHY link is blocked");
1875
1878 "Backoff generated at unexpected time");
1879 }
1880 else
1881 {
1882 // we are done checking the backoff
1883 m_backoffEndTime.reset();
1884 }
1885 }
1886
1888 {
1889 if (!m_backoffEndTime.has_value())
1890 {
1891 // this is the first time we set m_backoffEndTime, which is done right after receiving
1892 // a BlockAck, thus we have to wait an AIFS before invoking backoff
1894 m_staMacs[0]->GetChannelAccessManager(linkId)->GetSifs() +
1895 m_staMacs[0]->GetQosTxop(AC_BE)->GetAifsn(linkId) *
1896 m_staMacs[0]->GetChannelAccessManager(linkId)->GetSlot();
1897 }
1898 else
1899 {
1900 // we get here when the backoff expired but no transmission occurred, thus we have
1901 // generated a new backoff value and we will start decrementing the counter in a slot
1903 Simulator::Now() + m_staMacs[0]->GetChannelAccessManager(linkId)->GetSlot();
1904 }
1905 // add the time corresponding to the generated number of slots
1906 m_backoffEndTime.value() +=
1907 backoff * m_staMacs[0]->GetChannelAccessManager(linkId)->GetSlot();
1908 NS_LOG_DEBUG("Expected backoff end time = " << m_backoffEndTime->As(Time::US) << "\n");
1909 }
1910}
1911
1912void
1914 uint8_t phyId,
1915 WifiConstPsduMap psduMap,
1916 WifiTxVector txVector,
1917 double txPowerW)
1918{
1919 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
1920 auto linkId = m_txPsdus.back().linkId;
1921
1922 auto psdu = psduMap.begin()->second;
1923 auto nodeId = mac->GetDevice()->GetNode()->GetId();
1924
1925 switch (psdu->GetHeader(0).GetType())
1926 {
1928 NS_ASSERT_MSG(nodeId > 0, "APs do not send AssocReq frames");
1929 NS_TEST_EXPECT_MSG_EQ(+linkId, +m_mainPhyId, "AssocReq not sent by the main PHY");
1930 break;
1931
1932 case WIFI_MAC_CTL_RTS:
1933 CheckRtsFrames(*psdu->begin(), txVector, linkId);
1934 break;
1935
1936 case WIFI_MAC_CTL_CTS:
1937 CheckCtsFrames(*psdu->begin(), txVector, linkId);
1938 break;
1939
1940 case WIFI_MAC_QOSDATA:
1941 CheckQosFrames(psduMap, txVector, linkId);
1942 break;
1943
1945 CheckBlockAck(psduMap, txVector, linkId);
1946 break;
1947
1948 default:;
1949 }
1950}
1951
1952void
1954{
1955 // initially, we prevent transmissions on aux PHY links
1956 auto auxPhyLinks = m_staMacs[0]->GetSetupLinkIds();
1957 auxPhyLinks.erase(m_mainPhyId);
1958 if (m_nonEmlsrLink)
1959 {
1960 auxPhyLinks.erase(*m_nonEmlsrLink);
1961 }
1962 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
1963 m_apMac->GetAddress(),
1964 auxPhyLinks);
1965
1966 // Association, Block Ack agreement establishment and enabling EMLSR mode have been done.
1967 // After 50ms, schedule:
1968 // - block of transmissions on the link where the main PHY is operating and on the non-EMLSR
1969 // link (if any)
1970 // - the generation of two UL packets
1971 // - after m_unblockMainPhyLinkDelay, unblock transmissions on the link where the main PHY
1972 // is operating, so that the first data frame is transmitted on that link
1974 std::set<uint8_t> linkIds;
1975 linkIds.insert(*m_staMacs[0]->GetLinkForPhy(m_mainPhyId));
1976 if (m_nonEmlsrLink)
1977 {
1978 linkIds.insert(*m_nonEmlsrLink);
1979 }
1980 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
1981 m_apMac->GetAddress(),
1982 linkIds);
1983
1984 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
1985 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
1987
1989 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
1990 m_apMac->GetAddress(),
1991 {*m_staMacs[0]->GetLinkForPhy(m_mainPhyId)});
1992 });
1993 });
1994}
1995
1996void
1998 const WifiTxVector& txVector,
1999 uint8_t linkId)
2000{
2002
2003 auto txDuration =
2004 WifiPhy::CalculateTxDuration(psduMap, txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2005
2006 switch (m_countQoSframes)
2007 {
2008 case 1:
2009 case 2:
2010 // do nothing, these are the QoS data frames sent to establish BA agreements in DL and UL
2011 // direction
2012 break;
2013 case 3:
2014 // first UL data frame (transmitted by the main PHY)
2015 if (m_nonEmlsrLink)
2016 {
2017 // generate data packets for another UL data frame, which will be sent on the
2018 // non-EMLSR link
2019 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
2020 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
2021 GetApplication(UPLINK, 0, 2, 1000));
2022
2023 // unblock transmissions on the non-EMLSR link once the two packets are queued
2024 Simulator::ScheduleNow([=, this]() {
2025 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2026 m_apMac->GetAddress(),
2027 {*m_nonEmlsrLink});
2028 });
2029 }
2030
2031 // check that other EMLSR links are now blocked on the EMLSR client and on the AP MLD
2032 // after this QoS data frame is received
2033 Simulator::ScheduleNow([=, this]() {
2034 auto phyHdrTxTime = WifiPhy::CalculatePhyPreambleAndHeaderDuration(txVector);
2035 auto macHdrSize = (*psduMap.at(SU_STA_ID)->begin())->GetHeader().GetSerializedSize() +
2036 4 /* A-MPDU subframe header size */;
2037 auto macHdrTxTime =
2038 DataRate(txVector.GetMode().GetDataRate(txVector)).CalculateBytesTxTime(macHdrSize);
2039
2040 for (auto id : m_staMacs[0]->GetLinkIds())
2041 {
2043 m_staMacs[0],
2044 m_apMac->GetAddress(),
2045 id,
2046 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2047 id != m_staMacs[0]->GetLinkForPhy(m_mainPhyId) && m_staMacs[0]->IsEmlsrLink(id),
2048 "Checking EMLSR links on EMLSR client while sending the first data frame",
2049 false);
2050
2051 Simulator::Schedule(phyHdrTxTime + macHdrTxTime + MicroSeconds(1), [=, this]() {
2053 m_staMacs[0]->GetAddress(),
2054 id,
2055 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2056 id != m_staMacs[0]->GetLinkForPhy(m_mainPhyId) &&
2057 m_staMacs[0]->IsEmlsrLink(id),
2058 "Checking EMLSR links on AP MLD right after receiving the MAC "
2059 "header of the first data frame");
2060 });
2061
2062 Simulator::Schedule(txDuration + MAX_PROPAGATION_DELAY, [=, this]() {
2064 m_apMac,
2065 m_staMacs[0]->GetAddress(),
2066 id,
2067 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2068 id != m_staMacs[0]->GetLinkForPhy(m_mainPhyId) &&
2069 m_staMacs[0]->IsEmlsrLink(id),
2070 "Checking EMLSR links on AP MLD after sending the first data frame");
2071 });
2072 }
2073 });
2074
2075 if (m_nonEmlsrLink)
2076 {
2077 break;
2078 }
2079 m_countQoSframes++; // if all EMLSR links, next case is already executed now
2080 [[fallthrough]];
2081 case 4:
2082 // check that other EMLSR links are now blocked on the EMLSR client and on the AP MLD
2083 // after this QoS data frame is received
2084 Simulator::ScheduleNow([=, this]() {
2085 // make aux PHYs capable of transmitting frames
2086 auto auxPhyLinks = m_staMacs[0]->GetSetupLinkIds();
2087 auxPhyLinks.erase(m_mainPhyId);
2088 if (m_nonEmlsrLink)
2089 {
2090 auxPhyLinks.erase(*m_nonEmlsrLink);
2091 }
2092 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2093 m_apMac->GetAddress(),
2094 auxPhyLinks);
2095
2096 // block transmissions on the link where the main PHY is operating
2097 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2098 m_apMac->GetAddress(),
2099 {*m_staMacs[0]->GetLinkForPhy(m_mainPhyId)});
2100
2101 // generate data packets for another UL data frame, which will be sent on a link on
2102 // which an aux PHY is operating
2103 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
2104 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
2105 GetApplication(UPLINK, 0, 2, 1000));
2106 });
2107 break;
2108 case 5:
2109 // check that other EMLSR links are now blocked on both the EMLSR client and the AP MLD
2110 Simulator::ScheduleNow([=, this]() {
2111 for (auto id : m_staMacs[0]->GetLinkIds())
2112 {
2114 m_staMacs[0],
2115 m_apMac->GetAddress(),
2116 id,
2117 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2118 id != linkId && m_staMacs[0]->IsEmlsrLink(id),
2119 "Checking EMLSR links on EMLSR client while sending the second data frame",
2120 false);
2121
2123 m_apMac,
2124 m_staMacs[0]->GetAddress(),
2125 id,
2126 WifiQueueBlockedReason::USING_OTHER_EMLSR_LINK,
2127 id != linkId && m_staMacs[0]->IsEmlsrLink(id),
2128 "Checking EMLSR links on AP MLD while sending the second data frame",
2129 false);
2130 }
2131
2132 // unblock transmission on the link where the main PHY is operating
2133 m_staMacs[0]->GetMacQueueScheduler()->UnblockQueues(
2134 WifiQueueBlockedReason::TID_NOT_MAPPED,
2135 AC_BE,
2137 m_apMac->GetAddress(),
2138 m_staMacs[0]->GetAddress(),
2139 {0},
2140 {m_mainPhyId});
2141 });
2142 break;
2143 }
2144}
2145
2146void
2148 const WifiTxVector& txVector,
2149 uint8_t linkId)
2150{
2152
2153 auto auxPhyLinks = m_staMacs[0]->GetSetupLinkIds();
2154 auxPhyLinks.erase(m_mainPhyId);
2155 if (m_nonEmlsrLink)
2156 {
2157 auxPhyLinks.erase(*m_nonEmlsrLink);
2158 }
2159
2160 auto txDuration =
2161 WifiPhy::CalculateTxDuration(psduMap, txVector, m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2162
2163 // in this test, BlockAck frames terminates TXOP, thus aux PHYs shall be in sleep mode before
2164 // the end of BlockAck reception and awake right afterwards
2165 if (linkId != m_nonEmlsrLink)
2166 {
2167 Simulator::Schedule(txDuration - TimeStep(1),
2169 this,
2170 m_staMacs[0],
2171 true);
2172 Simulator::Schedule(txDuration + TimeStep(1),
2174 this,
2175 m_staMacs[0],
2176 false);
2177
2178 // if the TXOP has been carried out on a link other than the preferred link, the main PHY
2179 // switches back to the preferred link when the TXOP ends
2180 if (m_staMacs[0]->GetLinkForPhy(m_mainPhyId) != linkId)
2181 {
2182 Simulator::Schedule(txDuration + TimeStep(1), [=, this]() {
2183 CheckMainPhyTraceInfo(0, "TxopEnded", linkId, m_mainPhyId);
2184 });
2185 }
2186 }
2187
2188 switch (m_countBlockAck)
2189 {
2190 case 1:
2191 case 2:
2192 // do nothing, these are BlockAcks in response to the QoS data frames sent to establish
2193 // BA agreements in DL and UL direction
2194 break;
2195 case 3:
2196 if (linkId == m_nonEmlsrLink)
2197 {
2198 // this BlockAck has been sent on the non-EMLSR link, ignore it
2199 break;
2200 }
2201 m_checkBackoffStarted = true;
2202 if (!m_nonEmlsrLink)
2203 {
2204 m_countBlockAck++; // if all EMLSR links, next case is already executed now
2205 }
2206 [[fallthrough]];
2207 case 4:
2208 if (m_nonEmlsrLink && m_countBlockAck == 4)
2209 {
2210 // block transmissions on the non-EMLSR link
2211 Simulator::Schedule(txDuration + NanoSeconds(1), [=, this]() {
2212 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2213 m_apMac->GetAddress(),
2214 {*m_nonEmlsrLink});
2215 });
2216 }
2217 if (linkId == m_nonEmlsrLink)
2218 {
2219 // this BlockAck has been sent on the non-EMLSR link, ignore it
2220 break;
2221 }
2222 m_checkBackoffStarted = true;
2223 break;
2224 case 5:
2225 // Block Ack in response to the second data frame sent by the EMLSR client on EMLSR links.
2226 // Check that MediumSyncDelay timer starts running on the link where the main PHY switches
2227 // to when the channel switch is completed
2229 txDuration + m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId)->GetChannelSwitchDelay() +
2230 NanoSeconds(1),
2231 [=, this]() {
2232 auto elapsed =
2233 m_staMacs[0]->GetEmlsrManager()->GetElapsedMediumSyncDelayTimer(m_mainPhyId);
2235 elapsed.has_value(),
2236 true,
2237 "MediumSyncDelay timer not running on link where main PHY is operating");
2239 m_staMacs[0]->GetEmlsrManager()->GetMediumSyncDuration() -
2240 *elapsed;
2241 });
2242
2243 // Check that the number of backoff slots is not changed since the beginning of the TXOP
2244 Simulator::Schedule(txDuration, [=, this]() {
2245 m_checkBackoffStarted = false;
2247 true,
2248 "Backoff end time should have been calculated");
2249 // when this BlockAck is received, the TXOP ends and the main PHY link is unblocked,
2250 // which causes a new backoff timer to be generated if the backoff timer is not
2251 // already running
2253 });
2254
2255 // make aux PHYs not capable of transmitting frames
2256 m_staMacs[0]->BlockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2257 m_apMac->GetAddress(),
2258 auxPhyLinks);
2259
2260 // generate data packets for another UL data frame, which will be sent on the link where
2261 // the main PHY is operating
2262 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
2263 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
2264 break;
2265 case 6: {
2266 // block transmission on the main PHY link and on the non-EMLSR link (if any), so that
2267 // the next QoS frames are sent on a link where an aux PHY is operating
2268 std::set<uint8_t> linkIds{m_mainPhyId};
2269 if (m_nonEmlsrLink)
2270 {
2271 linkIds.insert(*m_nonEmlsrLink);
2272 }
2273 m_staMacs[0]->GetMacQueueScheduler()->BlockQueues(WifiQueueBlockedReason::TID_NOT_MAPPED,
2274 AC_BE,
2276 m_apMac->GetAddress(),
2277 m_staMacs[0]->GetAddress(),
2278 {0},
2279 linkIds);
2280 }
2281 // make sure aux PHYs are capable of transmitting frames
2282 m_staMacs[0]->UnblockUnicastTxOnLinks(WifiQueueBlockedReason::TID_NOT_MAPPED,
2283 m_apMac->GetAddress(),
2284 auxPhyLinks);
2285
2286 // generate data packets for another UL data frame
2287 NS_LOG_INFO("Enqueuing two packets at the EMLSR client\n");
2288 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(GetApplication(UPLINK, 0, 2, 1000));
2289 break;
2290 }
2291}
2292
2293void
2295 const WifiTxVector& txVector,
2296 uint8_t linkId)
2297{
2299 {
2300 // this function only considers RTS frames sent after the first QoS data frame
2301 return;
2302 }
2303
2304 if (linkId != m_mainPhyId)
2305 {
2306 if (m_countRtsframes > 0 && !m_corruptCts.has_value())
2307 {
2308 // we get here for the frame exchange in which the CTS response must be corrupted.
2309 // Install post reception error model on the STA affiliated with the EMLSR client that
2310 // is transmitting this RTS frame
2312 m_staMacs[0]->GetWifiPhy(linkId)->SetPostReceptionErrorModel(m_errorModel);
2313 m_corruptCts = true;
2314 }
2315
2316 return;
2317 }
2318
2319 // we get here for RTS frames sent by the main PHY while the MediumSyncDelay timer is running
2321
2323 m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId)->GetChannelWidth(),
2324 "RTS sent by main PHY on an unexpected width");
2325
2326 // corrupt reception at AP MLD
2327 NS_LOG_INFO("CORRUPTED");
2328 m_errorModel->SetList({mpdu->GetPacket()->GetUid()});
2329}
2330
2331void
2333 const WifiTxVector& txVector,
2334 uint8_t linkId)
2335{
2337 {
2338 // this function only considers CTS frames sent after the first QoS data frame
2339 return;
2340 }
2341
2342 auto txDuration = WifiPhy::CalculateTxDuration(mpdu->GetSize(),
2343 txVector,
2344 m_apMac->GetWifiPhy(linkId)->GetPhyBand());
2345 const auto doCorruptCts = m_corruptCts.has_value() && *m_corruptCts;
2346
2347 if (linkId != m_staMacs[0]->GetLinkForPhy(m_mainPhyId) && linkId != m_nonEmlsrLink &&
2348 mpdu->GetHeader().GetAddr1() == m_staMacs[0]->GetFrameExchangeManager(linkId)->GetAddress())
2349 {
2350 // this is a CTS sent to an aux PHY starting an UL TXOP. Given that aux PHYs do not
2351 // switch channel, they are put in sleep mode when the main PHY starts operating on their
2352 // link, which coincides with the end of CTS plus two propagation delays
2353 const auto auxPhy = m_staMacs[0]->GetWifiPhy(linkId);
2354 const auto mainPhy = m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId);
2355 Simulator::Schedule(txDuration, [=, this]() {
2356 // when CTS ends, the main PHY is still switching and the aux PHY is not yet sleeping
2357 NS_TEST_EXPECT_MSG_EQ(mainPhy->IsStateSwitching(),
2358 true,
2359 "Expecting the main PHY to be switching link");
2360 NS_TEST_EXPECT_MSG_EQ(auxPhy->IsStateSleep(),
2361 false,
2362 "Aux PHY on link " << +linkId << " already in sleep mode");
2363 // when CTS is sent, the main PHY may have already started switching, thus we may not
2364 // know which link the main PHY is moving from
2365 CheckMainPhyTraceInfo(0, "UlTxopRtsSentByAuxPhy", std::nullopt, linkId, false);
2366 });
2367 Simulator::Schedule(txDuration + (2 * MAX_PROPAGATION_DELAY) + TimeStep(1), [=, this]() {
2368 // aux PHYs are put to sleep if and only if CTS is not corrupted
2369 // (causing the end of the TXOP)
2370 CheckAuxPhysSleepMode(m_staMacs[0], !doCorruptCts);
2371 // if CTS is corrupted, TXOP ends and the main PHY switches back to the preferred
2372 // link, unless channel access is obtained on another link before the main PHY
2373 // completes the switch to the link on which CTS timeout occurred
2374 if (auto ehtFem = StaticCast<EhtFrameExchangeManager>(
2375 m_staMacs[0]->GetFrameExchangeManager(linkId));
2376 doCorruptCts && !ehtFem->UsingOtherEmlsrLink())
2377 {
2378 // check the traced elapsed time since CTS timeout before calling
2379 // CheckMainPhyTraceInfo
2380 if (const auto traceInfoIt = m_traceInfo.find(0);
2381 traceInfoIt != m_traceInfo.cend() &&
2382 traceInfoIt->second->GetName() == "CtsAfterRtsTimeout")
2383 {
2384 const auto& traceInfo =
2385 static_cast<const EmlsrCtsAfterRtsTimeoutTrace&>(*traceInfoIt->second);
2386 NS_TEST_EXPECT_MSG_GT(traceInfo.sinceCtsTimeout,
2387 Time{0},
2388 "Expected non-zero remaining time because main PHY "
2389 "was switching when CTS timeout occurred");
2390 }
2391
2392 CheckMainPhyTraceInfo(0, "CtsAfterRtsTimeout", linkId, m_mainPhyId);
2393 }
2394 });
2395 }
2396
2397 if (doCorruptCts)
2398 {
2399 // corrupt reception at EMLSR client
2400 NS_LOG_INFO("CORRUPTED");
2401 m_errorModel->SetList({mpdu->GetPacket()->GetUid()});
2402 m_corruptCts = false;
2403 }
2404}
2405
2406void
2416
2417void
2419{
2420 if (m_msdMaxNTxops > 0)
2421 {
2425 "Unexpected number of RTS frames sent while the MediumSyncDelay timer is running");
2426 }
2427
2428 auto psduIt = m_txPsdus.cbegin();
2429
2430 // lambda to jump to the next QoS data frame or MU-RTS Trigger Frame or RTS transmitted
2431 // to/by an EMLSR client
2432 auto jumpToQosDataOrMuRts = [&]() {
2433 while (psduIt != m_txPsdus.cend() &&
2434 !psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData() &&
2435 !psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts())
2436 {
2437 auto psdu = psduIt->psduMap.cbegin()->second;
2438 if (psdu->GetHeader(0).IsTrigger())
2439 {
2440 CtrlTriggerHeader trigger;
2441 psdu->GetPayload(0)->PeekHeader(trigger);
2442 if (trigger.IsMuRts())
2443 {
2444 break;
2445 }
2446 }
2447 psduIt++;
2448 }
2449 };
2450
2451 /**
2452 * EMLSR client with EMLSR mode enabled on all links (main PHY ID = 1).
2453 *
2454 * main PHY│
2455 * blocked,│
2456 * aux PHYs││main PHY blocked│
2457 * cannot │
2458 * transmit│
2459 * │ ┌───┐ ┌──┐
2460 * [link 0] │CTS│ │BA│
2461 * ────────────────────────┬───┬┴───┴┬───┬───┬┴──┴─────────────────────────────────────────
2462 * │RTS│ │QoS│QoS│
2463 * └───┘ │ 6 │ 7 │
2464 * └───┴───┘
2465 * gen backoff gen backoff if MediumSyncDelay
2466 * ┌──┐ (also many times) not running timer expired ┌──┐
2467 * [link 1] │BA│ │ if allowed │ │ │BA│
2468 * ─────────┬───┬───┬┴──┴───────────────────────────┬───┬─────┬───┬────┬───┬───┬┴──┴───────
2469 * │QoS│QoS│ │RTS│ ... │RTS│ │QoS│QoS│
2470 * │ 4 │ 5 │ └───┘ └───┘ │ 8 │ 9 │
2471 * └───┴───┘ └───┴───┘
2472 *
2473 * [link 2]
2474 * ───────────────────────────────────────────────────────────────────────────
2475 *
2476 *
2477 *
2478 * EMLSR client with EMLSR mode enabled on links 0 and 1 (main PHY ID = 1).
2479 *
2480 * main PHY │
2481 * and │
2482 * non-EMLSR│
2483 * link │
2484 * blocked,│
2485 * aux PHYs││main PHY blocked│
2486 * cannot │
2487 * transmit│
2488 * │ ┌───┐ ┌──┐
2489 * [link 0] │CTS│ │BA│
2490 * ────────────────────────┬───┬┴───┴┬───┬───┬┴──┴─────────────────────────────────────────
2491 * │RTS│ │QoS│QoS│
2492 * └───┘ │ 8 │ 9 │
2493 * └───┴───┘
2494 * gen backoff gen backoff if MediumSyncDelay
2495 * ┌──┐ (also many times) not running timer expired ┌──┐
2496 * [link 1] │BA│ │ if allowed │ │ │BA│
2497 * ─────────┬───┬───┬┴──┴───────────────────────────┬───┬─────┬───┬────┬───┬───┬┴──┴───────
2498 * │QoS│QoS│ │RTS│ ... │RTS│ │QoS│QoS│
2499 * │ 4 │ 5 │ └───┘ └───┘ │ 10│ 11│
2500 * └───┴───┘ └───┴───┘
2501 * ┌──┐
2502 * [link 2] │BA│
2503 * ──────────┬───┬───┬┴──┴────────────────────────────────────────────────────────────
2504 * │QoS│QoS│
2505 * │ 6 │ 7 │
2506 * └───┴───┘
2507 *
2508 * For both scenarios, after the last frame exchange on the main PHY link, we have the
2509 * following frame exchanges on an EMLSR link where an aux PHY is operating on.
2510 *
2511 *
2512 * [ link ] ┌───┐ ┌───┐ ┌──┐
2513 * [0 or 2] │CTS│ │CTS│ │BA│
2514 * ──────┬───┬┴───X────┬───┬┴───┴┬───┬───┬┴──┴─────────────────────────────────────────
2515 * │RTS│ │RTS│ │QoS│QoS│
2516 * └───┘ └───┘ │ X │ Y │
2517 * └───┴───┘
2518 * For all EMLSR links scenario, X=10, Y=11
2519 * For the scenario with a non-EMLSR link, X=12, Y=13
2520 */
2521
2522 // jump to the first (non-Beacon) frame transmitted after establishing BA agreements and
2523 // enabling EMLSR mode
2524 while (psduIt != m_txPsdus.cend() &&
2525 (psduIt->startTx < m_firstUlPktsGenTime ||
2526 psduIt->psduMap.cbegin()->second->GetHeader(0).IsBeacon()))
2527 {
2528 ++psduIt;
2529 }
2530
2531 // the first QoS data frame is transmitted by the main PHY without RTS protection as soon
2532 // as transmissions on the link where the main PHY is operating are unblocked (at this
2533 // moment, aux PHYs cannot transmit)
2534 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
2535 true,
2536 "First QoS data frame has not been transmitted");
2537 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
2538 true,
2539 "First QoS data frame should be transmitted without protection");
2540 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
2541 +m_mainPhyId,
2542 "First QoS data frame should be transmitted by the main PHY");
2543 NS_TEST_EXPECT_MSG_GT_OR_EQ(psduIt->startTx,
2545 "First QoS data frame sent too early");
2546
2547 auto prevPsduIt = psduIt++;
2548 jumpToQosDataOrMuRts();
2549
2550 if (m_nonEmlsrLink)
2551 {
2552 // an additional data frame is sent concurrently on the non-EMLSR link
2554 (psduIt != m_txPsdus.cend()),
2555 true,
2556 "Expected another QoS data frame sent concurrently with the first frame");
2558 psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
2559 true,
2560 "First data frame on non-EMLSR link should be transmitted without protection");
2561 NS_TEST_EXPECT_MSG_EQ(+psduIt->linkId,
2562 +m_nonEmlsrLink.value(),
2563 "First data frame expected to be transmitted on the non-EMLSR link");
2564 const auto txDuration =
2565 WifiPhy::CalculateTxDuration(prevPsduIt->psduMap,
2566 prevPsduIt->txVector,
2567 m_staMacs[0]->GetWifiPhy(prevPsduIt->phyId)->GetPhyBand());
2568 NS_TEST_EXPECT_MSG_LT(psduIt->startTx,
2569 prevPsduIt->startTx + txDuration,
2570 "First data frame on the non-EMLSR link not sent concurrently");
2571 psduIt++;
2572 jumpToQosDataOrMuRts();
2573 }
2574
2575 // the second QoS data frame is transmitted by the main PHY after that the aux PHY has
2576 // obtained a TXOP and sent an RTS
2577 // RTS
2578 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
2579 true,
2580 "RTS before second QoS data frame has not been transmitted");
2581 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(),
2582 true,
2583 "Second QoS data frame should be transmitted with protection");
2585 +psduIt->phyId,
2586 +m_mainPhyId,
2587 "RTS before second QoS data frame should not be transmitted by the main PHY");
2588 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
2590 "RTS before second data frame transmitted on an unexpected width");
2591 psduIt++;
2592 // CTS
2593 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
2594 true,
2595 "CTS before second QoS data frame has not been transmitted");
2596 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(),
2597 true,
2598 "CTS before second QoS data frame has not been transmitted");
2599 psduIt++;
2600 // QoS Data
2601 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
2602 true,
2603 "Second QoS data frame has not been transmitted");
2604 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
2605 true,
2606 "Second QoS data frame has not been transmitted");
2607 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
2608 +m_mainPhyId,
2609 "Second QoS data frame should be transmitted by the main PHY");
2610 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
2612 "Second data frame not transmitted on the same width as RTS");
2613
2614 bool moreQosDataFound = false;
2615
2616 while (++psduIt != m_txPsdus.cend())
2617 {
2618 jumpToQosDataOrMuRts();
2619 if (psduIt != m_txPsdus.cend() &&
2620 psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData())
2621 {
2622 moreQosDataFound = true;
2623
2624 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
2625 +m_mainPhyId,
2626 "Third QoS data frame should be transmitted by the main PHY");
2627 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
2628 m_staMacs[0]->GetDevice()->GetPhy(m_mainPhyId)->GetChannelWidth(),
2629 "Expecting TX width of third data frame to equal the channel "
2630 "width used by the main PHY");
2632 psduIt->startTx,
2634 "Third QoS data frame sent before MediumSyncDelay timer expired");
2635
2636 break;
2637 }
2638 }
2639
2640 NS_TEST_EXPECT_MSG_EQ(moreQosDataFound,
2641 true,
2642 "Third QoS data frame transmitted by the main PHY not found");
2643
2644 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()), true, "Expected more frames");
2645 ++psduIt;
2646 jumpToQosDataOrMuRts();
2647
2648 // the first attempt at transmitting the fourth QoS data frame fails because CTS is corrupted
2649 // RTS
2650 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
2651 true,
2652 "RTS before fourth QoS data frame has not been transmitted");
2653 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(),
2654 true,
2655 "Fourth QoS data frame should be transmitted with protection");
2657 +psduIt->phyId,
2658 +m_mainPhyId,
2659 "RTS before fourth QoS data frame should not be transmitted by the main PHY");
2660 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
2662 "RTS before fourth data frame transmitted on an unexpected width");
2663 psduIt++;
2664 // CTS
2665 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
2666 true,
2667 "CTS before fourth QoS data frame has not been transmitted");
2668 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(),
2669 true,
2670 "CTS before fourth QoS data frame has not been transmitted");
2671 psduIt++;
2672 jumpToQosDataOrMuRts();
2673
2674 // the fourth QoS data frame is transmitted by an aux PHY after that the aux PHY has
2675 // obtained a TXOP and sent an RTS
2676 // RTS
2677 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
2678 true,
2679 "RTS before fourth QoS data frame has not been transmitted");
2680 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsRts(),
2681 true,
2682 "Fourth QoS data frame should be transmitted with protection");
2684 +psduIt->phyId,
2685 +m_mainPhyId,
2686 "RTS before fourth QoS data frame should not be transmitted by the main PHY");
2687 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
2689 "RTS before fourth data frame transmitted on an unexpected width");
2690 psduIt++;
2691 // CTS
2692 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
2693 true,
2694 "CTS before fourth QoS data frame has not been transmitted");
2695 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsCts(),
2696 true,
2697 "CTS before fourth QoS data frame has not been transmitted");
2698 psduIt++;
2699 // QoS Data
2700 NS_TEST_ASSERT_MSG_EQ((psduIt != m_txPsdus.cend()),
2701 true,
2702 "Fourth QoS data frame has not been transmitted");
2703 NS_TEST_EXPECT_MSG_EQ(psduIt->psduMap.cbegin()->second->GetHeader(0).IsQosData(),
2704 true,
2705 "Fourth QoS data frame has not been transmitted");
2706 NS_TEST_EXPECT_MSG_EQ(+psduIt->phyId,
2707 +m_mainPhyId,
2708 "Fourth QoS data frame should be transmitted by the main PHY");
2709 NS_TEST_EXPECT_MSG_EQ(psduIt->txVector.GetChannelWidth(),
2711 "Fourth data frame not transmitted on the same width as RTS");
2712}
2713
2715 : EmlsrOperationsTestBase("Check UL OFDMA operations with an EMLSR client"),
2716 m_enableBsrp(enableBsrp),
2717 m_txPsdusPos(0),
2718 m_startAccessReq(0)
2719{
2720 m_linksToEnableEmlsrOn = {0, 1, 2};
2721 m_nEmlsrStations = 1;
2723 m_establishBaDl = {};
2724 m_establishBaUl = {0};
2725 m_mainPhyId = 1;
2726 m_duration = Seconds(1.0);
2727}
2728
2729void
2731{
2732 Config::SetDefault("ns3::WifiPhy::ChannelSwitchDelay", TimeValue(m_transitionDelay.at(0)));
2733
2735
2736 m_apMac->GetQosTxop(AC_BE)->SetTxopLimits(
2737 {MicroSeconds(3200), MicroSeconds(3200), MicroSeconds(3200)});
2738
2739 auto muScheduler = CreateObjectWithAttributes<RrMultiUserScheduler>("EnableUlOfdma",
2740 BooleanValue(true),
2741 "EnableBsrp",
2743 m_apMac->AggregateObject(muScheduler);
2744}
2745
2746void
2748 uint8_t phyId,
2749 WifiConstPsduMap psduMap,
2750 WifiTxVector txVector,
2751 double txPowerW)
2752{
2753 EmlsrOperationsTestBase::Transmit(mac, phyId, psduMap, txVector, txPowerW);
2754 auto linkId = m_txPsdus.back().linkId;
2755
2756 auto psdu = psduMap.begin()->second;
2757
2758 switch (psdu->GetHeader(0).GetType())
2759 {
2762 {
2763 // this is the first Trigger Frame sent after the AP requested channel access
2764 // through the Multi-user scheduler and it is an ICF for the EMLSR client
2765 m_txPsdusPos = m_txPsdus.size() - 1;
2766 auto txDuration = WifiPhy::CalculateTxDuration(psduMap,
2767 txVector,
2768 mac->GetWifiPhy(linkId)->GetPhyBand());
2769 NS_LOG_INFO("This is the first Trigger Frame\n");
2770 // once the Trigger Frame is received by the EMLSR client, make the client application
2771 // on the EMLSR client generate two packets. These packets will be sent via UL OFDMA
2772 // because the EMLSR client has blocked transmissions on other links when receiving
2773 // this Trigger Frame, hence it will not try to get access on other links via EDCA
2775 txDuration + MicroSeconds(1), // to account for propagation delay
2776 [=, this]() {
2777 for (const auto id : m_staMacs[0]->GetLinkIds())
2778 {
2780 m_staMacs[0]->GetFrameExchangeManager(id));
2782 ehtFem->UsingOtherEmlsrLink(),
2783 (id != linkId),
2784 "Link " << +id << " was" << (id == linkId ? " not" : "")
2785 << " expected to be blocked on EMLSR client at time "
2786 << Simulator::Now().As(Time::NS));
2787 }
2788 NS_LOG_INFO("Generate two packets\n");
2789 m_staMacs[0]->GetDevice()->GetNode()->AddApplication(
2790 GetApplication(UPLINK, 0, 2, 100));
2791 });
2792 }
2793 break;
2794
2797 {
2798 CtrlBAckResponseHeader blockAck;
2799 psdu->GetPayload(0)->PeekHeader(blockAck);
2800 if (blockAck.IsMultiSta())
2801 {
2802 auto txDuration =
2804 txVector,
2805 mac->GetWifiPhy(linkId)->GetPhyBand());
2806 Simulator::Stop(txDuration + MicroSeconds(1));
2807 }
2808 }
2809 break;
2810
2811 default:;
2812 }
2813
2814 if (psdu->GetHeader(0).IsCfEnd())
2815 {
2816 // we do not check CF-End frames
2817 m_txPsdus.pop_back();
2818 }
2819}
2820
2821void
2831
2832void
2834{
2835 auto muScheduler = m_apMac->GetObject<MultiUserScheduler>();
2836 NS_TEST_ASSERT_MSG_NE(muScheduler, nullptr, "No MU scheduler installed on AP MLD");
2837
2838 NS_LOG_INFO("Setting Access Request interval");
2839
2840 const auto interval = MilliSeconds(50);
2841 muScheduler->SetAccessReqInterval(interval);
2842 m_startAccessReq = Simulator::Now() + interval;
2843}
2844
2845void
2847{
2848 /**
2849 * Sending BSRP TF disabled.
2850 *
2851 * The figure assumes that link 0 is used to send the first Trigger Frame after that the
2852 * AP MLD requests channel access through the Multi-user scheduler. The first Trigger Frame
2853 * is MU-RTS because EMLSR client needs an ICF; the other Trigger Frames are Basic TFs and
2854 * do not solicit the EMLSR client.
2855 * ┌─────┐ ┌─────┐ ┌──────┐
2856 * │ MU │ │Basic│ │Multi-│
2857 * [link 0] │ RTS │ │ TF │ │STA BA│
2858 * ───────────┴─────┴┬───┬┴─────┴┬────────┬─┴──────┴───────────────
2859 * │CTS│ │QoS Null│
2860 * ├───┤ ├────────┤
2861 * │CTS│ │QoS Data│
2862 * └───┘ └────────┘
2863 *
2864 * ┌─────┐
2865 * │Basic│
2866 * [link 1] │ TF │
2867 * ─────────────┴─────┴┬────┬──────────────────────────────────────
2868 * │QoS │
2869 * │Null│
2870 * └────┘
2871 *
2872 * ┌─────┐
2873 * │Basic│
2874 * [link 2] │ TF │
2875 * ─────────────┴─────┴┬────┬──────────────────────────────────────
2876 * │QoS │
2877 * │Null│
2878 * └────┘
2879 *
2880 * Sending BSRP TF enabled.
2881 *
2882 * The figure assumes that link 0 is used to send the first Trigger Frame after that the
2883 * AP MLD requests channel access through the Multi-user scheduler. The first Trigger Frames
2884 * are all BSRP Trigger Frames, but only the first one solicits the EMLSR client, too.
2885 * ┌─────┐ ┌─────┐ ┌──────┐
2886 * │BSRP │ │Basic│ │Multi-│
2887 * [link 0] │ TF │ │ TF │ │STA BA│
2888 * ───────────┴─────┴┬────────┬┴─────┴┬────────┬─┴──────┴──────────
2889 * │QoS Null│ │QoS Data│
2890 * ├────────┤ └────────┘
2891 * │QoS Null│
2892 * └────────┘
2893 *
2894 * ┌─────┐
2895 * │BSRP │
2896 * [link 1] │ TF │
2897 * ─────────────┴─────┴┬────┬──────────────────────────────────────
2898 * │QoS │
2899 * │Null│
2900 * └────┘
2901 *
2902 * ┌─────┐
2903 * │BSRP │
2904 * [link 2] │ TF │
2905 * ─────────────┴─────┴┬────┬──────────────────────────────────────
2906 * │QoS │
2907 * │Null│
2908 * └────┘
2909 */
2910
2911 NS_TEST_ASSERT_MSG_GT(m_txPsdusPos, 0, "First Trigger Frame not detected");
2912
2913 // Check the Trigger Frames (one per link) after requesting channel access
2914 auto index = m_txPsdusPos;
2915 const auto firstLinkId = m_txPsdus[m_txPsdusPos].linkId;
2916 for (; index < m_txPsdusPos + 3; ++index)
2917 {
2918 NS_TEST_ASSERT_MSG_EQ(m_txPsdus[index].psduMap.cbegin()->second->GetHeader(0).IsTrigger(),
2919 true,
2920 "Expected a Trigger Frame");
2921 CtrlTriggerHeader trigger;
2922 m_txPsdus[index].psduMap.cbegin()->second->GetPayload(0)->PeekHeader(trigger);
2923
2924 TriggerFrameType triggerType =
2925 m_enableBsrp ? TriggerFrameType::BSRP_TRIGGER
2926 : (index == m_txPsdusPos ? TriggerFrameType::MU_RTS_TRIGGER
2927 : TriggerFrameType::BASIC_TRIGGER);
2928 NS_TEST_EXPECT_MSG_EQ(+static_cast<uint8_t>(trigger.GetType()),
2929 +static_cast<uint8_t>(triggerType),
2930 "Unexpected Trigger Frame type on link " << +m_txPsdus[index].linkId);
2931
2932 // only the first TF solicits the EMLSR client and the non-AP MLD
2934 trigger.GetNUserInfoFields(),
2935 (index == m_txPsdusPos ? 2 : 1),
2936 "Unexpected number of User Info fields for Trigger Frame, index=" << index);
2937 }
2938
2939 auto startIndex = index;
2940 std::size_t ctsCount = 0;
2941 std::size_t qosNullCount = 0;
2942 // Check responses to Trigger Frames
2943 for (; index < startIndex + 4; ++index)
2944 {
2945 const auto& hdr = m_txPsdus[index].psduMap.cbegin()->second->GetHeader(0);
2946
2947 if (hdr.IsCts())
2948 {
2949 ++ctsCount;
2950 continue;
2951 }
2952
2953 if (hdr.IsQosData() && !hdr.HasData())
2954 {
2955 ++qosNullCount;
2956 // if BSRP is enabled, the QoS Null frame sent by the EMLSR client in response to the
2957 // first BSRP TF reports a non-null buffer status
2958 if (m_enableBsrp &&
2959 hdr.GetAddr2() == m_staMacs[0]->GetFrameExchangeManager(firstLinkId)->GetAddress())
2960 {
2961 NS_TEST_EXPECT_MSG_GT(+hdr.GetQosQueueSize(), 0, "Unexpected buffer size");
2962 }
2963 else
2964 {
2965 NS_TEST_EXPECT_MSG_EQ(+hdr.GetQosQueueSize(), 0, "Unexpected buffer size");
2966 }
2967 continue;
2968 }
2969 }
2970 NS_TEST_EXPECT_MSG_EQ(ctsCount, (m_enableBsrp ? 0 : 2), "Unexpected number of CTS frames");
2971 NS_TEST_EXPECT_MSG_EQ(qosNullCount,
2972 (m_enableBsrp ? 4 : 2),
2973 "Unexpected number of QoS Null frames");
2974
2975 // we expect only one Basic Trigger Frame (sent on the same link as the first Trigger Frame),
2976 // because the buffer status reported on the other links by the non-EMLSR client is zero
2977 NS_TEST_ASSERT_MSG_EQ(m_txPsdus[index].psduMap.cbegin()->second->GetHeader(0).IsTrigger(),
2978 true,
2979 "Expected a Trigger Frame");
2980 NS_TEST_EXPECT_MSG_EQ(+m_txPsdus[index].linkId,
2981 +firstLinkId,
2982 "Unexpected link ID for Basic TF");
2983 CtrlTriggerHeader trigger;
2984 m_txPsdus[index].psduMap.cbegin()->second->GetPayload(0)->PeekHeader(trigger);
2985
2986 NS_TEST_EXPECT_MSG_EQ(+static_cast<uint8_t>(trigger.GetType()),
2987 +static_cast<uint8_t>(TriggerFrameType::BASIC_TRIGGER),
2988 "Unexpected Trigger Frame type");
2989
2990 // when BSRP TF is enabled, the non-EMLSR client has already communicated a buffer status of
2991 // zero, so it is not solicited by the AP through the Basic Trigger Frame. Otherwise, it is
2992 // solicited because buffer status was not known when the BSRP TF was prepared (before sending
2993 // MU-RTS)
2995 (m_enableBsrp ? 1 : 2),
2996 "Unexpected number of User Info fields for Basic Trigger Frame");
2997
2998 // Response(s) to the Basic Trigger Frame
2999 startIndex = ++index;
3000 for (; index < startIndex + (m_enableBsrp ? 1 : 2); ++index)
3001 {
3002 const auto& hdr = m_txPsdus[index].psduMap.cbegin()->second->GetHeader(0);
3003
3004 NS_TEST_EXPECT_MSG_EQ(hdr.IsQosData(), true, "Expected a QoS frame");
3005
3006 // EMLSR client sends a QoS Data frame, non-EMLSR client sends a QoS Null frame
3008 hdr.HasData(),
3009 (hdr.GetAddr2() == m_staMacs[0]->GetFrameExchangeManager(firstLinkId)->GetAddress()),
3010 "Unexpected type of QoS data frame");
3011
3012 if (hdr.HasData())
3013 {
3014 NS_TEST_EXPECT_MSG_EQ(m_txPsdus[index].txVector.IsUlMu(),
3015 true,
3016 "QoS Data frame should be sent in a TB PPDU");
3017 }
3018 }
3019
3020 // Finally, the AP MLD sends a Multi-STA BlockAck
3021 NS_TEST_EXPECT_MSG_EQ(m_txPsdus[index].psduMap.cbegin()->second->GetHeader(0).IsBlockAck(),
3022 true,
3023 "Expected a BlockAck frame");
3024 CtrlBAckResponseHeader blockAck;
3025 m_txPsdus[index].psduMap.cbegin()->second->GetPayload(0)->PeekHeader(blockAck);
3026 NS_TEST_EXPECT_MSG_EQ(blockAck.IsMultiSta(), true, "Expected a Multi-STA BlockAck");
3027}
3028
3030 : TestSuite("wifi-emlsr-basic-exchanges", Type::UNIT)
3031{
3032 for (const auto& emlsrLinks :
3033 {std::set<uint8_t>{0, 1, 2}, std::set<uint8_t>{1, 2}, std::set<uint8_t>{0, 1}})
3034 {
3036 0,
3037 emlsrLinks,
3038 {MicroSeconds(32)},
3039 {MicroSeconds(32)},
3040 MicroSeconds(512),
3041 true /* putAuxPhyToSleep */}),
3042 TestCase::Duration::QUICK);
3044 1,
3045 emlsrLinks,
3046 {MicroSeconds(64)},
3047 {MicroSeconds(64)},
3048 MicroSeconds(512),
3049 false /* putAuxPhyToSleep */}),
3050 TestCase::Duration::QUICK);
3052 2,
3053 emlsrLinks,
3054 {MicroSeconds(128), MicroSeconds(256)},
3055 {MicroSeconds(128), MicroSeconds(256)},
3056 MicroSeconds(512),
3057 true /* putAuxPhyToSleep */}),
3058 TestCase::Duration::QUICK);
3059 }
3060
3061 for (auto genBackoffIfTxopWithoutTx : {true, false})
3062 {
3063 AddTestCase(new EmlsrUlTxopTest({{0, 1, 2},
3064 MHz_u{40},
3065 MHz_u{20},
3066 MicroSeconds(5504),
3067 3,
3068 genBackoffIfTxopWithoutTx,
3069 true, /* putAuxPhyToSleep */
3070 false /* switchMainPhyBackDelayTimeout */}),
3071 TestCase::Duration::QUICK);
3072 AddTestCase(new EmlsrUlTxopTest({{0, 1},
3073 MHz_u{40},
3074 MHz_u{20},
3075 MicroSeconds(5504),
3076 1,
3077 genBackoffIfTxopWithoutTx,
3078 false, /* putAuxPhyToSleep */
3079 true /* switchMainPhyBackDelayTimeout */}),
3080 TestCase::Duration::QUICK);
3081 }
3082
3083 AddTestCase(new EmlsrUlOfdmaTest(false), TestCase::Duration::QUICK);
3084 AddTestCase(new EmlsrUlOfdmaTest(true), TestCase::Duration::QUICK);
3085}
3086
#define Max(a, b)
Test the transmission of DL frames to EMLSR clients.
void CheckInitialControlFrame(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken by the AP MLD transmitting an initial Control frame to an EM...
const Time m_fe2to3delay
time interval between 2nd and 3rd frame exchange sequences after the enablement of EMLSR mode
void CheckResults()
Check that the simulation produced the expected results.
void CheckPmModeAfterAssociation(const Mac48Address &address)
Check that the AP MLD considers the correct Power Management mode for the links setup with the given ...
EmlsrDlTxopTest(const Params &params)
Constructor.
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
Ptr< ListErrorModel > m_errorModel
error rate model to corrupt BlockAck at AP MLD
void CheckStaEmlNotificationFrame(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken when an EMLSR client transmits an EML Operating Mode Notific...
std::size_t m_countQoSframes
counter for QoS frames (transition delay test)
void CheckApEmlNotificationFrame(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken when the AP MLD transmits an EML Operating Mode Notification...
Time m_emlsrEnabledTime
when EMLSR mode has been enabled on all EMLSR clients
std::set< uint8_t > m_emlsrLinks
IDs of the links on which EMLSR mode has to be enabled.
void DoSetup() override
Implementation to do any local setup required for this TestCase.
void CheckQosFrames(const WifiConstPsduMap &psduMap, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken by the AP MLD transmitting a PPDU containing QoS data frames...
void Transmit(Ptr< WifiMac > mac, uint8_t phyId, WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW) override
Callback invoked when a FEM passes PSDUs to the PHY.
void EnableEmlsrMode()
Enable EMLSR mode on the next EMLSR client.
void CheckBlockAck(const WifiConstPsduMap &psduMap, const WifiTxVector &txVector, uint8_t phyId)
Check that appropriate actions are taken by the AP MLD receiving a PPDU containing BlockAck frames fr...
void DoRun() override
Implementation to actually run this TestCase.
std::size_t m_countBlockAck
counter for BlockAck frames (transition delay test)
Base class for EMLSR Operations tests.
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.
bool m_putAuxPhyToSleep
whether aux PHYs are put to sleep during DL/UL TXOPs
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.
uint16_t m_lastAid
AID of last associated station.
std::vector< Time > m_transitionDelay
Transition Delay advertised by the non-AP MLD.
void CheckMainPhyTraceInfo(std::size_t index, std::string_view reason, const std::optional< uint8_t > &fromLinkId, uint8_t toLinkId, bool checkFromLinkId=true, bool checkToLinkId=true)
Check information provided by the EMLSR Manager MainPhySwitch trace.
std::map< std::size_t, std::shared_ptr< EmlsrMainPhySwitchTrace > > m_traceInfo
EMLSR client ID-indexed map of trace info from last main PHY switch.
Time m_transitionTimeout
Transition Timeout advertised by the AP MLD.
void CheckAuxPhysSleepMode(Ptr< StaWifiMac > staMac, bool sleep)
Check whether aux PHYs of the given device are in sleep mode/awake.
std::vector< Ptr< StaWifiMac > > m_staMacs
MACs of the non-AP MLDs.
std::vector< uint8_t > m_establishBaUl
the TIDs for which BA needs to be established with the AP as recipient
Check UL OFDMA operations with EMLSR clients.
Time m_startAccessReq
start time of the first AP MLD access request via MU scheduler
void DoRun() override
Implementation to actually run this TestCase.
void CheckResults()
Check that the simulation produced the expected results.
EmlsrUlOfdmaTest(bool enableBsrp)
Constructor.
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
void Transmit(Ptr< WifiMac > mac, uint8_t phyId, WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW) override
Callback invoked when a FEM passes PSDUs to the PHY.
void DoSetup() override
Implementation to do any local setup required for this TestCase.
bool m_enableBsrp
whether MU scheduler sends BSRP TFs
std::size_t m_txPsdusPos
position in the vector of TX PSDUs of the first ICF
Test the transmission of UL frames from EMLSR clients.
std::size_t m_countQoSframes
counter for QoS frames
const Time m_unblockMainPhyLinkDelay
delay between the time the first two UL packets are generated and the time transmissions are unblocke...
Ptr< ListErrorModel > m_errorModel
error rate model to corrupt packets
MHz_u m_auxPhyChannelWidth
max width supported by aux PHYs
void BackoffGenerated(uint32_t backoff, uint8_t linkId)
Callback invoked when a new backoff value is generated by the EMLSR client.
std::optional< uint8_t > m_nonEmlsrLink
ID of the non-EMLSR link (if any)
Time m_lastMsdExpiryTime
expiry time of the last MediumSyncDelay timer
void DoSetup() override
Implementation to do any local setup required for this TestCase.
std::size_t m_countRtsframes
counter for RTS frames
void CheckCtsFrames(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken by the EMLSR client when receiving a CTS frame on the given ...
void DoRun() override
Implementation to actually run this TestCase.
Time m_firstUlPktsGenTime
generation time of the first two UL packets
std::optional< bool > m_corruptCts
whether the transmitted CTS must be corrupted
void CheckBlockAck(const WifiConstPsduMap &psduMap, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken when an MLD transmits a PPDU containing BlockAck frames on t...
void StartTraffic() override
Start the generation of traffic (needs to be overridden)
std::optional< Time > m_backoffEndTime
expected backoff end time on main PHY link
MHz_u m_channelWidth
width of the channels used by MLDs
std::set< uint8_t > m_emlsrLinks
IDs of the links on which EMLSR mode has to be enabled.
Time m_mediumSyncDuration
duration of the MediumSyncDelay timer
void CheckRtsFrames(Ptr< const WifiMpdu > mpdu, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken by the EMLSR client when transmitting an RTS frame on the gi...
void CheckQosFrames(const WifiConstPsduMap &psduMap, const WifiTxVector &txVector, uint8_t linkId)
Check that appropriate actions are taken when an MLD transmits a PPDU containing QoS data frames on t...
bool m_genBackoffIfTxopWithoutTx
whether the backoff should be invoked when the AC gains the right to start a TXOP but it does not tra...
void Transmit(Ptr< WifiMac > mac, uint8_t phyId, WifiConstPsduMap psduMap, WifiTxVector txVector, double txPowerW) override
Callback invoked when a FEM passes PSDUs to the PHY.
bool m_checkBackoffStarted
whether we are checking the generated backoff values
std::size_t m_countBlockAck
counter for BlockAck frames
void CheckResults()
Check that the simulation produced the expected results.
uint8_t m_msdMaxNTxops
Max number of TXOPs that an EMLSR client is allowed to attempt to initiate while the MediumSyncDelay ...
EmlsrUlTxopTest(const Params &params)
Constructor.
wifi EMLSR suite to test basic frame exchanges.
A container for one type of attribute.
AttributeValue implementation for Boolean.
Definition boolean.h:26
Headers for BlockAck response.
Headers for Trigger frames.
bool IsMuRts() const
Check if this is a MU-RTS Trigger frame.
void SetPaddingSize(std::size_t size)
Set the size in bytes of the Padding field.
TriggerFrameType GetType() const
Get the Trigger Frame type.
std::size_t GetNUserInfoFields() const
Get the number of User Info fields in this Trigger Frame.
Class for representing data rates.
Definition data-rate.h:78
Time CalculateBytesTxTime(uint32_t bytes) const
Calculate transmission time.
Definition data-rate.cc:220
Hold variables of type enum.
Definition enum.h:52
void SetList(const std::list< uint64_t > &packetlist)
an EUI-48 address
static Mac48Address GetBroadcast()
Implement the header for Action frames of type EML Operating Mode Notification.
EmlControl m_emlControl
EML Control field.
MultiUserScheduler is an abstract base class defining the API that APs supporting at least VHT can us...
Smart pointer class similar to boost::intrusive_ptr.
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
void AddTestCase(TestCase *testCase, Duration duration=Duration::QUICK)
Add an individual child TestCase to this test suite.
Definition test.cc:292
A suite of tests to run.
Definition test.h:1267
Type
Type of test.
Definition test.h:1274
Simulation virtual time values and global simulation resolution.
Definition nstime.h:94
TimeWithUnit As(const Unit unit=Time::AUTO) const
Attach a unit to a Time, to facilitate output in a specific unit.
Definition time.cc:403
bool IsStrictlyPositive() const
Exactly equivalent to t > 0.
Definition nstime.h:340
@ US
microsecond
Definition nstime.h:107
@ NS
nanosecond
Definition nstime.h:108
bool IsZero() const
Exactly equivalent to t == 0.
Definition nstime.h:304
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.
static std::pair< CategoryValue, ActionValue > Remove(Ptr< Packet > pkt)
Remove an Action header from the given packet.
Implements the IEEE 802.11 MAC header.
uint64_t GetDataRate(MHz_u channelWidth, Time guardInterval, uint8_t nss) const
Definition wifi-mode.cc:110
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.
WifiPreamble GetPreambleType() const
MHz_u GetChannelWidth() const
#define NS_ASSERT_MSG(condition, message)
At runtime, in debugging builds, if this condition is not true, the program prints the message to out...
Definition assert.h:75
void SetDefault(std::string name, const AttributeValue &value)
Definition config.cc:886
#define NS_ABORT_MSG_IF(cond, msg)
Abnormal program termination if a condition is true, with a message.
Definition abort.h:97
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition log.h:191
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
Definition log.h:257
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition log.h:264
Ptr< T > CreateObject(Args &&... args)
Create an object by type, with varying number of constructor parameters.
Definition object.h:619
Ptr< T > CreateObjectWithAttributes(Args... args)
Allocate an Object on the heap and initialize with a set of attributes.
Ptr< T > Create(Ts &&... args)
Create class instances by constructors with varying numbers of arguments and return them by Ptr.
Definition ptr.h:436
#define NS_TEST_EXPECT_MSG_GT_OR_EQ(actual, limit, msg)
Test that an actual value is greater than or equal to limit and report if not.
Definition test.h:986
#define NS_TEST_ASSERT_MSG_EQ(actual, limit, msg)
Test that an actual and expected (limit) value are equal and report and abort if not.
Definition test.h:134
#define NS_TEST_EXPECT_MSG_LT_OR_EQ(actual, limit, msg)
Test that an actual value is less than or equal to a limit and report if not.
Definition test.h:820
#define NS_TEST_EXPECT_MSG_LT(actual, limit, msg)
Test that an actual value is less than a limit and report if not.
Definition test.h:780
#define NS_TEST_EXPECT_MSG_GT(actual, limit, msg)
Test that an actual value is greater than a limit and report if not.
Definition test.h:946
#define NS_TEST_EXPECT_MSG_NE(actual, limit, msg)
Test that an actual and expected (limit) value are not equal and report if not.
Definition test.h:656
#define NS_TEST_EXPECT_MSG_EQ(actual, limit, msg)
Test that an actual and expected (limit) value are equal and report if not.
Definition test.h:241
#define NS_TEST_ASSERT_MSG_NE(actual, limit, msg)
Test that an actual and expected (limit) value are not equal and report and abort if not.
Definition test.h:554
#define NS_TEST_ASSERT_MSG_GT(actual, limit, msg)
Test that an actual value is greater than a limit and report and abort if not.
Definition test.h:864
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
TriggerFrameType
The different Trigger frame types.
@ WIFI_PREAMBLE_HT_MF
@ WIFI_PHY_BAND_6GHZ
The 6 GHz band.
@ WIFI_PHY_BAND_2_4GHZ
The 2.4 GHz band.
@ WIFI_PHY_BAND_5GHZ
The 5 GHz band.
@ AC_BE
Best Effort.
Definition qos-utils.h:64
Every class exported by the ns3 library is enclosed in the ns3 namespace.
const Time MAX_PROPAGATION_DELAY
maximum propagation delay
Callback< R, Args... > MakeCallback(R(T::*memPtr)(Args...), OBJ objPtr)
Build Callbacks for class method members which take varying numbers of arguments and potentially retu...
Definition callback.h:684
bool IsTrigger(const WifiPsduMap &psduMap)
Ptr< T1 > DynamicCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:580
std::size_t Count20MHzSubchannels(MHz_u channelWidth)
Return the number of 20 MHz subchannels covering the channel width.
Definition wifi-utils.h:138
@ WIFI_MAC_CTL_TRIGGER
@ WIFI_MAC_CTL_RTS
@ WIFI_MAC_CTL_CTS
@ WIFI_MAC_MGT_ACTION
@ WIFI_MAC_MGT_ASSOCIATION_REQUEST
@ WIFI_MAC_CTL_BACKRESP
@ WIFI_MAC_CTL_END
@ WIFI_MAC_QOSDATA
Ptr< T1 > StaticCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:587
std::unordered_map< uint16_t, Ptr< const WifiPsdu > > WifiConstPsduMap
Map of const PSDUs indexed by STA-ID.
Definition wifi-ppdu.h:38
uint32_t GetAckSize()
Return the total Ack size (including FCS trailer).
Definition wifi-utils.cc:51
static constexpr uint16_t SU_STA_ID
STA_ID to identify a single user (SU)
STL namespace.
phy
Definition third.py:78
Parameters for the EMLSR DL TXOP test.
Parameters for the EMLSR UL TXOP test.
Struct to trace that main PHY started switching after a CTS timeout occurred on the link on which an ...
static WifiEmlsrBasicExchangesTestSuite g_wifiEmlsrBasicExchangesTestSuite
the test suite