A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
he-frame-exchange-manager.cc
Go to the documentation of this file.
1/*
2 * Copyright (c) 2020 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 "he-configuration.h"
12#include "he-phy.h"
14
15#include "ns3/abort.h"
16#include "ns3/ap-wifi-mac.h"
17#include "ns3/erp-ofdm-phy.h"
18#include "ns3/log.h"
19#include "ns3/recipient-block-ack-agreement.h"
20#include "ns3/snr-tag.h"
21#include "ns3/sta-wifi-mac.h"
22#include "ns3/wifi-mac-queue.h"
23#include "ns3/wifi-mac-trailer.h"
24
25#include <algorithm>
26#include <functional>
27
28#undef NS_LOG_APPEND_CONTEXT
29#define NS_LOG_APPEND_CONTEXT WIFI_FEM_NS_LOG_APPEND_CONTEXT
30
31namespace ns3
32{
33
34NS_LOG_COMPONENT_DEFINE("HeFrameExchangeManager");
35
36NS_OBJECT_ENSURE_REGISTERED(HeFrameExchangeManager);
37
38bool
39IsTrigger(const WifiPsduMap& psduMap)
40{
41 return psduMap.size() == 1 && psduMap.cbegin()->first == SU_STA_ID &&
42 psduMap.cbegin()->second->GetNMpdus() == 1 &&
43 psduMap.cbegin()->second->GetHeader(0).IsTrigger();
44}
45
46bool
48{
49 return psduMap.size() == 1 && psduMap.cbegin()->first == SU_STA_ID &&
50 psduMap.cbegin()->second->GetNMpdus() == 1 &&
51 psduMap.cbegin()->second->GetHeader(0).IsTrigger();
52}
53
54TypeId
56{
57 static TypeId tid =
58 TypeId("ns3::HeFrameExchangeManager")
60 .AddConstructor<HeFrameExchangeManager>()
61 .SetGroupName("Wifi")
62 .AddAttribute("ContinueTxopAfterBsrp",
63 "Whether to continue a TXOP a SIFS after the reception of responses "
64 "to a BSRP Trigger Frame when TXOP limit is zero.",
65 BooleanValue(false),
68 return tid;
69}
70
72 : m_intraBssNavEnd(0),
73 m_triggerFrameInAmpdu(false)
74{
75 NS_LOG_FUNCTION(this);
76}
77
82
83void
94
95void
102
103void
105{
106 NS_LOG_FUNCTION(this << txVector << psduDuration.As(Time::MS));
107 VhtFrameExchangeManager::RxStartIndication(txVector, psduDuration);
108 // Cancel intra-BSS NAV reset timer when receiving a frame from the PHY
110}
111
112void
114{
115 NS_LOG_FUNCTION(this);
116 m_apMac = nullptr;
117 m_staMac = nullptr;
118 m_psduMap.clear();
120 m_muScheduler = nullptr;
123}
124
125void
127{
129 NS_ABORT_MSG_IF(!m_apMac, "A Multi-User Scheduler can only be aggregated to an AP");
130 NS_ABORT_MSG_IF(!m_apMac->GetHeConfiguration(),
131 "A Multi-User Scheduler can only be aggregated to an HE AP");
132 m_muScheduler = muScheduler;
133}
134
135bool
136HeFrameExchangeManager::StartFrameExchange(Ptr<QosTxop> edca, Time availableTime, bool initialFrame)
137{
138 NS_LOG_FUNCTION(this << edca << availableTime << initialFrame);
139
142
143 /*
144 * We consult the Multi-user Scheduler (if available) to know the type of transmission to make
145 * if:
146 * - there is no pending BlockAckReq to transmit
147 * - either the AC queue is empty (the scheduler might select an UL MU transmission)
148 * or the next frame in the AC queue is a non-broadcast QoS data frame addressed to
149 * a receiver with which a BA agreement has been already established
150 */
151 if (m_muScheduler && !GetBar(edca->GetAccessCategory()) &&
152 (!(mpdu = edca->PeekNextMpdu(m_linkId)) ||
153 (mpdu->GetHeader().IsQosData() && !mpdu->GetHeader().GetAddr1().IsGroup() &&
154 m_mac->GetBaAgreementEstablishedAsOriginator(mpdu->GetHeader().GetAddr1(),
155 mpdu->GetHeader().GetQosTid()))))
156 {
157 txFormat = m_muScheduler->NotifyAccessGranted(edca,
158 availableTime,
159 initialFrame,
161 m_linkId);
162 }
163
164 if (txFormat == MultiUserScheduler::SU_TX)
165 {
166 return VhtFrameExchangeManager::StartFrameExchange(edca, availableTime, initialFrame);
167 }
168
169 if (txFormat == MultiUserScheduler::DL_MU_TX)
170 {
171 if (m_muScheduler->GetDlMuInfo(m_linkId).psduMap.empty())
172 {
174 "The Multi-user Scheduler returned DL_MU_TX with empty psduMap, do not transmit");
175 return false;
176 }
177
179 m_muScheduler->GetDlMuInfo(m_linkId).txParams);
180 return true;
181 }
182
183 if (txFormat == MultiUserScheduler::UL_MU_TX)
184 {
185 auto packet = Create<Packet>();
186 packet->AddHeader(m_muScheduler->GetUlMuInfo(m_linkId).trigger);
187 auto trigger = Create<WifiMpdu>(packet, m_muScheduler->GetUlMuInfo(m_linkId).macHdr);
190 {SU_STA_ID,
191 GetWifiPsdu(trigger, m_muScheduler->GetUlMuInfo(m_linkId).txParams.m_txVector)}},
192 m_muScheduler->GetUlMuInfo(m_linkId).txParams);
193 return true;
194 }
195
196 return false;
197}
198
199void
201{
202 NS_LOG_FUNCTION(this << &txParams);
203
204 m_psduMap = std::move(psduMap);
205 m_txParams = std::move(txParams);
206
207 // Make sure that the acknowledgment time has been computed, so that SendMuRts()
208 // can reuse this value.
210
211 if (!m_txParams.m_acknowledgment->acknowledgmentTime.has_value())
212 {
214 }
215
216 // in case we are sending a Trigger Frame, update the acknowledgment time so that
217 // the Duration/ID of the MU-RTS is correctly computed
219 {
221 const auto& trigger = m_muScheduler->GetUlMuInfo(m_linkId).trigger;
222 NS_ASSERT_MSG(!trigger.IsBasic() || m_txParams.m_acknowledgment->method ==
224 "Acknowledgment (" << m_txParams.m_acknowledgment.get()
225 << ") incompatible with Basic Trigger Frame");
226 NS_ASSERT_MSG(!trigger.IsBsrp() ||
228 "Acknowledgment (" << m_txParams.m_acknowledgment.get()
229 << ") incompatible with BSRP Trigger Frame");
230 // Add a SIFS and the TB PPDU duration to the acknowledgment time of the Trigger Frame
231 auto txVector = trigger.GetHeTbTxVector(trigger.begin()->GetAid12());
232 *m_txParams.m_acknowledgment->acknowledgmentTime +=
234 txVector,
235 m_phy->GetPhyBand());
236 }
237
238 // Set QoS Ack policy
239 for (auto& psdu : m_psduMap)
240 {
242 }
243
244 for (const auto& psdu : m_psduMap)
245 {
246 for (const auto& mpdu : *PeekPointer(psdu.second))
247 {
248 if (mpdu->IsQueued())
249 {
250 mpdu->SetInFlight(m_linkId);
251 }
252 }
253 }
254
256}
257
258void
260{
261 NS_LOG_FUNCTION(this << &txParams);
262
263 NS_ABORT_MSG_IF(m_psduMap.size() > 1 &&
264 txParams.m_protection->method == WifiProtection::RTS_CTS,
265 "Cannot use RTS/CTS with MU PPDUs");
266 if (txParams.m_protection->method == WifiProtection::MU_RTS_CTS)
267 {
268 auto protection = static_cast<WifiMuRtsCtsProtection*>(txParams.m_protection.get());
269
270 NS_ASSERT(protection->muRts.IsMuRts());
271 NS_ASSERT(m_sentRtsTo.empty());
272 m_sentRtsTo = GetTfRecipients(protection->muRts);
273
274 SendMuRts(txParams);
275 }
276 else
277 {
279 }
280}
281
282std::set<Mac48Address>
284{
285 std::set<Mac48Address> recipients;
286 NS_ASSERT_MSG(m_apMac, "APs only can send Trigger Frames");
287 const auto& aidAddrMap = m_apMac->GetStaList(m_linkId);
288
289 for (const auto& userInfo : trigger)
290 {
291 const auto addressIt = aidAddrMap.find(userInfo.GetAid12());
292 NS_ASSERT_MSG(addressIt != aidAddrMap.end(), "AID not found");
293 recipients.insert(addressIt->second);
294 }
295
296 return recipients;
297}
298
299void
301{
302 NS_LOG_FUNCTION(this);
303 if (!m_psduMap.empty())
304 {
306 m_sentRtsTo.clear();
307 if (m_muScheduler)
308 {
309 m_muScheduler->NotifyProtectionCompleted(m_linkId, m_psduMap, m_txParams);
310
311 if (m_psduMap.empty())
312 {
313 NS_LOG_INFO("Multi-user scheduler aborted the transmission");
316 {
318 return;
319 }
321 m_edca = nullptr;
322 return;
323 }
324 }
326 {
327 SendPsduMap();
328 }
329 else
330 {
332 }
333 return;
334 }
336}
337
338Time
340 const WifiTxVector& muRtsTxVector,
341 Time txDuration,
342 Time response) const
343{
344 NS_LOG_FUNCTION(this << muRtsSize << muRtsTxVector << txDuration << response);
345
347 {
348 WifiTxVector txVector;
349 txVector.SetMode(GetCtsModeAfterMuRts());
350 return VhtFrameExchangeManager::GetRtsDurationId(txVector, txDuration, response);
351 }
352
353 // under multiple protection settings, if the TXOP limit is not null, Duration/ID
354 // is set to cover the remaining TXOP time (Sec. 9.2.5.2 of 802.11-2016).
355 // The TXOP holder may exceed the TXOP limit in some situations (Sec. 10.22.2.8
356 // of 802.11-2016)
357 return std::max(m_edca->GetRemainingTxop(m_linkId) -
358 WifiPhy::CalculateTxDuration(muRtsSize, muRtsTxVector, m_phy->GetPhyBand()),
359 Seconds(0));
360}
361
362void
364{
365 NS_LOG_FUNCTION(this << &txParams);
366 WifiMacHeader hdr;
369 hdr.SetAddr2(m_self);
370 hdr.SetDsNotTo();
371 hdr.SetDsNotFrom();
372 hdr.SetNoRetry();
373 hdr.SetNoMoreFragments();
374
375 NS_ASSERT(txParams.m_protection && txParams.m_protection->method == WifiProtection::MU_RTS_CTS);
376 auto protection = static_cast<WifiMuRtsCtsProtection*>(txParams.m_protection.get());
377
378 NS_ASSERT(protection->muRts.IsMuRts());
379 protection->muRts.SetCsRequired(true);
380 Ptr<Packet> payload = Create<Packet>();
381 payload->AddHeader(protection->muRts);
382
383 auto mpdu = Create<WifiMpdu>(payload, hdr);
384
385 NS_ASSERT(txParams.m_txDuration.has_value());
386 NS_ASSERT(txParams.m_acknowledgment->acknowledgmentTime.has_value());
387 mpdu->GetHeader().SetDuration(
388 GetMuRtsDurationId(mpdu->GetSize(),
389 protection->muRtsTxVector,
390 *txParams.m_txDuration,
391 *txParams.m_acknowledgment->acknowledgmentTime));
392
393 // Get the TXVECTOR used by one station to send the CTS response. This is used
394 // to compute the preamble duration, so it does not matter which station we choose
395 WifiTxVector ctsTxVector =
396 GetCtsTxVectorAfterMuRts(protection->muRts, protection->muRts.begin()->GetAid12());
397
398 // After transmitting an MU-RTS frame, the STA shall wait for a CTSTimeout interval of
399 // aSIFSTime + aSlotTime + aRxPHYStartDelay (Sec. 27.2.5.2 of 802.11ax D3.0).
400 // aRxPHYStartDelay equals the time to transmit the PHY header.
401 Time timeout = WifiPhy::CalculateTxDuration(mpdu->GetSize(),
402 protection->muRtsTxVector,
403 m_phy->GetPhyBand()) +
404 m_phy->GetSifs() + m_phy->GetSlot() +
406
409 timeout,
412 this,
413 mpdu,
414 protection->muRtsTxVector);
415 m_channelAccessManager->NotifyCtsTimeoutStartNow(timeout);
416
417 ForwardMpduDown(mpdu, protection->muRtsTxVector);
418}
419
420void
422{
423 NS_LOG_FUNCTION(this << *muRts << txVector);
424 DoCtsAfterMuRtsTimeout(muRts, txVector, true);
425}
426
427void
429 const WifiTxVector& txVector,
430 bool updateFailedCw)
431{
432 NS_LOG_FUNCTION(this << *muRts << txVector << updateFailedCw);
433
434 if (m_psduMap.empty())
435 {
436 // A CTS Timeout occurred when protecting a single PSDU that is not included
437 // in a DL MU PPDU is handled by the parent classes
439 return;
440 }
441
442 m_sentRtsTo.clear();
443 for (const auto& psdu : m_psduMap)
444 {
445 for (const auto& mpdu : *PeekPointer(psdu.second))
446 {
447 if (mpdu->IsQueued())
448 {
449 mpdu->ResetInFlight(m_linkId);
450 }
451 }
452 }
453
454 if (const auto& hdr = m_psduMap.cbegin()->second->GetHeader(0); !hdr.GetAddr1().IsGroup())
455 {
456 GetWifiRemoteStationManager()->ReportRtsFailed(hdr);
457 }
458
459 for (const auto& [staId, psdu] : m_psduMap)
460 {
461 if (psdu->GetAddr1().IsGroup())
462 {
463 continue;
464 }
465 if (auto droppedMpdu = DropMpduIfRetryLimitReached(psdu))
466 {
467 GetWifiRemoteStationManager()->ReportFinalRtsFailed(droppedMpdu->GetHeader());
468 }
469 // Make the sequence numbers of the MPDUs available again if the MPDUs have never
470 // been transmitted, both in case the MPDUs have been discarded and in case the
471 // MPDUs have to be transmitted (because a new sequence number is assigned to
472 // MPDUs that have never been transmitted and are selected for transmission)
474 }
475
476 m_psduMap.clear();
477 TransmissionFailed(!updateFailedCw);
478}
479
482{
483 auto it = std::find_if(
484 psduMap.begin(),
485 psduMap.end(),
486 [&to](std::pair<uint16_t, Ptr<WifiPsdu>> psdu) { return psdu.second->GetAddr1() == to; });
487 if (it != psduMap.end())
488 {
489 return it->second;
490 }
491 return nullptr;
492}
493
494void
496{
497 NS_LOG_FUNCTION(this << *rts << txVector);
498
499 if (m_psduMap.empty())
500 {
501 // A CTS Timeout occurred when protecting a single PSDU that is not included
502 // in a DL MU PPDU is handled by the parent classes
504 return;
505 }
506
507 NS_ABORT_MSG_IF(m_psduMap.size() > 1, "RTS/CTS cannot be used to protect an MU PPDU");
508 DoCtsTimeout(m_psduMap.begin()->second);
509 m_psduMap.clear();
510}
511
512void
514{
515 NS_LOG_FUNCTION(this);
516
517 // A multi-user transmission may succeed even if some stations did not respond.
518 // Remove such stations from the set of stations for which protection is not needed
519 // in the current TXOP.
520 for (const auto& address : m_txTimer.GetStasExpectedToRespond())
521 {
522 NS_LOG_DEBUG(address << " did not respond, hence it is no longer protected");
523 m_protectedStas.erase(address);
524 m_sentFrameTo.erase(address);
525 }
526
531 {
532 NS_LOG_DEBUG("Schedule another transmission in a SIFS after successful BSRP TF");
533 Simulator::Schedule(m_phy->GetSifs(), [=, this]() {
534 // TXOP limit is null, hence the txopDuration parameter is unused
535 if (!StartTransmission(m_edca, Seconds(0)))
536 {
537 SendCfEndIfNeeded();
538 }
539 });
541 {
543 }
544 m_sentFrameTo.clear();
545 }
546 else
547 {
549 }
550}
551
552void
553HeFrameExchangeManager::SendPsduMap()
554{
555 NS_LOG_FUNCTION(this);
556
557 NS_ASSERT(m_txParams.m_acknowledgment);
558 NS_ASSERT(!m_txTimer.IsRunning());
559
560 WifiTxTimer::Reason timerType = WifiTxTimer::NOT_RUNNING; // no timer
561 WifiTxVector* responseTxVector = nullptr;
562 Ptr<WifiMpdu> mpdu = nullptr;
563 Ptr<WifiPsdu> psdu = nullptr;
564 WifiTxVector txVector;
565 std::set<Mac48Address> staExpectResponseFrom;
566
567 // Compute the type of TX timer to set depending on the acknowledgment method
568
569 /*
570 * Acknowledgment via a sequence of BlockAckReq and BlockAck frames
571 */
572 if (m_txParams.m_acknowledgment->method == WifiAcknowledgment::DL_MU_BAR_BA_SEQUENCE)
573 {
574 auto acknowledgment =
575 static_cast<WifiDlMuBarBaSequence*>(m_txParams.m_acknowledgment.get());
576
577 // schedule the transmission of required BlockAckReq frames
578 for (const auto& psdu : m_psduMap)
579 {
580 if (acknowledgment->stationsSendBlockAckReqTo.contains(psdu.second->GetAddr1()))
581 {
582 // the receiver of this PSDU will receive a BlockAckReq
583 std::set<uint8_t> tids = psdu.second->GetTids();
584 NS_ABORT_MSG_IF(tids.size() > 1,
585 "Acknowledgment method incompatible with a Multi-TID A-MPDU");
586 uint8_t tid = *tids.begin();
587
588 NS_ASSERT(m_edca);
589 auto [reqHdr, hdr] =
590 m_mac->GetQosTxop(tid)->PrepareBlockAckRequest(psdu.second->GetAddr1(), tid);
591 m_edca->GetBaManager()->ScheduleBar(reqHdr, hdr);
592 }
593 }
594
595 if (!acknowledgment->stationsReplyingWithNormalAck.empty())
596 {
597 // a station will reply immediately with a Normal Ack
598 timerType = WifiTxTimer::WAIT_NORMAL_ACK_AFTER_DL_MU_PPDU;
599 responseTxVector =
600 &acknowledgment->stationsReplyingWithNormalAck.begin()->second.ackTxVector;
601 auto from = acknowledgment->stationsReplyingWithNormalAck.begin()->first;
602 psdu = GetPsduTo(from, m_psduMap);
603 NS_ASSERT(psdu->GetNMpdus() == 1);
604 mpdu = *psdu->begin();
605 staExpectResponseFrom.insert(from);
606 }
607 else if (!acknowledgment->stationsReplyingWithBlockAck.empty())
608 {
609 // a station will reply immediately with a Block Ack
610 timerType = WifiTxTimer::WAIT_BLOCK_ACK;
611 responseTxVector =
612 &acknowledgment->stationsReplyingWithBlockAck.begin()->second.blockAckTxVector;
613 auto from = acknowledgment->stationsReplyingWithBlockAck.begin()->first;
614 psdu = GetPsduTo(from, m_psduMap);
615 staExpectResponseFrom.insert(from);
616 }
617 // else no station will reply immediately
618 }
619 /*
620 * Acknowledgment via a MU-BAR Trigger Frame sent as single user frame
621 */
622 else if (m_txParams.m_acknowledgment->method == WifiAcknowledgment::DL_MU_TF_MU_BAR)
623 {
624 auto acknowledgment = static_cast<WifiDlMuTfMuBar*>(m_txParams.m_acknowledgment.get());
625
626 if (!m_triggerFrame)
627 {
628 // we are transmitting the DL MU PPDU and have to schedule the
629 // transmission of a MU-BAR Trigger Frame.
630 // Create a TRIGVECTOR by "merging" all the BlockAck TXVECTORs
631 std::map<uint16_t, CtrlBAckRequestHeader> recipients;
632
633 NS_ASSERT(!acknowledgment->stationsReplyingWithBlockAck.empty());
634 auto staIt = acknowledgment->stationsReplyingWithBlockAck.begin();
635 m_trigVector = staIt->second.blockAckTxVector;
636 while (staIt != acknowledgment->stationsReplyingWithBlockAck.end())
637 {
638 NS_ASSERT(m_apMac);
639 uint16_t staId = m_apMac->GetAssociationId(staIt->first, m_linkId);
640
641 m_trigVector.SetHeMuUserInfo(staId,
642 staIt->second.blockAckTxVector.GetHeMuUserInfo(staId));
643 recipients.emplace(staId, staIt->second.barHeader);
644
645 staIt++;
646 }
647 // set the Length field of the response TXVECTOR, which is needed to correctly
648 // set the UL Length field of the MU-BAR Trigger Frame
649 m_trigVector.SetLength(acknowledgment->ulLength);
650
651 m_triggerFrame = PrepareMuBar(m_trigVector, recipients);
652 }
653 else
654 {
655 // we are transmitting the MU-BAR following the DL MU PPDU after a SIFS.
656 // m_psduMap and m_txParams are still the same as when the DL MU PPDU was sent.
657 // record the set of stations expected to send a BlockAck frame
658 for (auto& station : acknowledgment->stationsReplyingWithBlockAck)
659 {
660 staExpectResponseFrom.insert(station.first);
661 }
662
663 Ptr<WifiPsdu> triggerPsdu = GetWifiPsdu(m_triggerFrame, acknowledgment->muBarTxVector);
664 Time txDuration = WifiPhy::CalculateTxDuration(triggerPsdu->GetSize(),
665 acknowledgment->muBarTxVector,
666 m_phy->GetPhyBand());
667 // update acknowledgmentTime to correctly set the Duration/ID
668 *acknowledgment->acknowledgmentTime -= (m_phy->GetSifs() + txDuration);
669 m_triggerFrame->GetHeader().SetDuration(GetPsduDurationId(txDuration, m_txParams));
670
671 responseTxVector =
672 &acknowledgment->stationsReplyingWithBlockAck.begin()->second.blockAckTxVector;
673 Time timeout = txDuration + m_phy->GetSifs() + m_phy->GetSlot() +
674 m_phy->CalculatePhyPreambleAndHeaderDuration(*responseTxVector);
675
676 m_txTimer.Set(WifiTxTimer::WAIT_BLOCK_ACKS_IN_TB_PPDU,
677 timeout,
678 staExpectResponseFrom,
679 &HeFrameExchangeManager::BlockAcksInTbPpduTimeout,
680 this,
681 &m_psduMap,
682 staExpectResponseFrom.size());
683 m_channelAccessManager->NotifyAckTimeoutStartNow(timeout);
684
685 ForwardPsduDown(triggerPsdu, acknowledgment->muBarTxVector);
686
687 // Pass TRIGVECTOR to HE PHY (equivalent to PHY-TRIGGER.request primitive)
688 auto hePhy =
689 StaticCast<HePhy>(m_phy->GetPhyEntity(responseTxVector->GetModulationClass()));
690 hePhy->SetTrigVector(m_trigVector, timeout);
691
692 return;
693 }
694 }
695 /*
696 * Acknowledgment requested by MU-BAR TFs aggregated to PSDUs in the DL MU PPDU
697 */
698 else if (m_txParams.m_acknowledgment->method == WifiAcknowledgment::DL_MU_AGGREGATE_TF)
699 {
700 auto acknowledgment = static_cast<WifiDlMuAggregateTf*>(m_txParams.m_acknowledgment.get());
701
702 m_trigVector =
703 acknowledgment->stationsReplyingWithBlockAck.begin()->second.blockAckTxVector;
704
705 // record the set of stations expected to send a BlockAck frame
706 for (auto& station : acknowledgment->stationsReplyingWithBlockAck)
707 {
708 staExpectResponseFrom.insert(station.first);
709 // check that the station that is expected to send a BlockAck frame is
710 // actually the receiver of a PSDU
711 auto psduMapIt = std::find_if(m_psduMap.begin(),
712 m_psduMap.end(),
713 [&station](std::pair<uint16_t, Ptr<WifiPsdu>> psdu) {
714 return psdu.second->GetAddr1() == station.first;
715 });
716
717 NS_ASSERT(psduMapIt != m_psduMap.end());
718 // add a MU-BAR Trigger Frame to the PSDU
719 std::vector<Ptr<WifiMpdu>> mpduList(psduMapIt->second->begin(),
720 psduMapIt->second->end());
721 NS_ASSERT(mpduList.size() == psduMapIt->second->GetNMpdus());
722 // set the Length field of the response TXVECTOR, which is needed to correctly
723 // set the UL Length field of the MU-BAR Trigger Frame
724 station.second.blockAckTxVector.SetLength(acknowledgment->ulLength);
725 mpduList.push_back(PrepareMuBar(station.second.blockAckTxVector,
726 {{psduMapIt->first, station.second.barHeader}}));
727 psduMapIt->second = Create<WifiPsdu>(std::move(mpduList));
728 m_trigVector.SetHeMuUserInfo(
729 psduMapIt->first,
730 station.second.blockAckTxVector.GetHeMuUserInfo(psduMapIt->first));
731 }
732
733 timerType = WifiTxTimer::WAIT_BLOCK_ACKS_IN_TB_PPDU;
734 responseTxVector =
735 &acknowledgment->stationsReplyingWithBlockAck.begin()->second.blockAckTxVector;
736 m_trigVector.SetLength(acknowledgment->ulLength);
737 }
738 /*
739 * Basic Trigger Frame starting an UL MU transmission
740 */
741 else if (m_txParams.m_acknowledgment->method == WifiAcknowledgment::UL_MU_MULTI_STA_BA)
742 {
743 // the PSDU map being sent must contain a (Basic) Trigger Frame
744 NS_ASSERT(IsTrigger(m_psduMap));
745 mpdu = *m_psduMap.begin()->second->begin();
746
747 auto acknowledgment = static_cast<WifiUlMuMultiStaBa*>(m_txParams.m_acknowledgment.get());
748
749 // record the set of stations solicited by this Trigger Frame
750 for (const auto& station : acknowledgment->stationsReceivingMultiStaBa)
751 {
752 staExpectResponseFrom.insert(station.first.first);
753 }
754
755 // Reset stationsReceivingMultiStaBa, which will be filled as soon as
756 // TB PPDUs are received
757 acknowledgment->stationsReceivingMultiStaBa.clear();
758 acknowledgment->baType.m_bitmapLen.clear();
759
760 timerType = WifiTxTimer::WAIT_TB_PPDU_AFTER_BASIC_TF;
761 responseTxVector = &acknowledgment->tbPpduTxVector;
762 m_trigVector = GetTrigVector(m_muScheduler->GetUlMuInfo(m_linkId).trigger);
763 }
764 /*
765 * BSRP Trigger Frame
766 */
767 else if (m_txParams.m_acknowledgment->method == WifiAcknowledgment::NONE &&
768 !m_txParams.m_txVector.IsUlMu() && IsTrigger(m_psduMap))
769 {
770 CtrlTriggerHeader& trigger = m_muScheduler->GetUlMuInfo(m_linkId).trigger;
771 NS_ASSERT(trigger.IsBsrp());
772 NS_ASSERT(m_apMac);
773
774 // record the set of stations solicited by this Trigger Frame
775 for (const auto& userInfo : trigger)
776 {
777 auto staIt = m_apMac->GetStaList(m_linkId).find(userInfo.GetAid12());
778 NS_ASSERT(staIt != m_apMac->GetStaList(m_linkId).end());
779 staExpectResponseFrom.insert(staIt->second);
780 }
781
782 timerType = WifiTxTimer::WAIT_QOS_NULL_AFTER_BSRP_TF;
783 txVector = trigger.GetHeTbTxVector(trigger.begin()->GetAid12());
784 responseTxVector = &txVector;
785 m_trigVector = GetTrigVector(m_muScheduler->GetUlMuInfo(m_linkId).trigger);
786 }
787 /*
788 * TB PPDU solicited by a Basic Trigger Frame
789 */
790 else if (m_txParams.m_txVector.IsUlMu() &&
791 m_txParams.m_acknowledgment->method == WifiAcknowledgment::ACK_AFTER_TB_PPDU)
792 {
793 NS_ASSERT(m_psduMap.size() == 1);
794 timerType = WifiTxTimer::WAIT_BLOCK_ACK_AFTER_TB_PPDU;
795 NS_ASSERT(m_staMac && m_staMac->IsAssociated());
796 auto recv = m_psduMap.begin()->second->GetAddr1();
797 txVector = GetWifiRemoteStationManager()->GetBlockAckTxVector(recv, m_txParams.m_txVector);
798 responseTxVector = &txVector;
799 staExpectResponseFrom.insert(recv);
800 }
801 /*
802 * QoS Null frames solicited by a BSRP Trigger Frame
803 */
804 else if (m_txParams.m_txVector.IsUlMu() &&
805 m_txParams.m_acknowledgment->method == WifiAcknowledgment::NONE)
806 {
807 // No response is expected, so do nothing.
808 }
809 else
810 {
811 NS_ABORT_MSG("Unable to handle the selected acknowledgment method ("
812 << m_txParams.m_acknowledgment.get() << ")");
813 }
814
815 // create a map of Ptr<const WifiPsdu>, as required by the PHY
816 WifiConstPsduMap psduMap;
817 for (const auto& psdu : m_psduMap)
818 {
819 psduMap.emplace(psdu.first, psdu.second);
820 }
821
822 Time txDuration;
823 if (m_txParams.m_txVector.IsUlMu())
824 {
825 txDuration = HePhy::ConvertLSigLengthToHeTbPpduDuration(m_txParams.m_txVector.GetLength(),
826 m_txParams.m_txVector,
827 m_phy->GetPhyBand());
828 }
829 else
830 {
831 txDuration =
832 WifiPhy::CalculateTxDuration(psduMap, m_txParams.m_txVector, m_phy->GetPhyBand());
833
834 // Set Duration/ID
835 Time durationId = GetPsduDurationId(txDuration, m_txParams);
836
837 if (m_continueTxopAfterBsrpTf && m_edca && m_edca->GetTxopLimit(m_linkId).IsZero() &&
838 timerType == WifiTxTimer::WAIT_QOS_NULL_AFTER_BSRP_TF)
839 {
840 // add the duration of the following frame exchange to extend the NAV beyond the
841 // responses to the BSRP TF
842 durationId += m_muScheduler->GetExtraTimeForBsrpTfDurationId(m_linkId);
843 }
844
845 for (auto& psdu : m_psduMap)
846 {
847 psdu.second->SetDuration(durationId);
848 }
849 }
850
851 if (timerType == WifiTxTimer::NOT_RUNNING)
852 {
853 if (m_triggerFrame)
854 {
855 NS_LOG_DEBUG("Scheduling MU-BAR " << *m_triggerFrame);
856 Simulator::Schedule(txDuration + m_phy->GetSifs(),
857 &HeFrameExchangeManager::SendPsduMap,
858 this);
859 }
860 else if (!m_txParams.m_txVector.IsUlMu())
861 {
862 Simulator::Schedule(txDuration, &HeFrameExchangeManager::TransmissionSucceeded, this);
863 }
864 }
865 else
866 {
867 Time timeout = txDuration + m_phy->GetSifs() + m_phy->GetSlot() +
868 m_phy->CalculatePhyPreambleAndHeaderDuration(*responseTxVector);
869 m_channelAccessManager->NotifyAckTimeoutStartNow(timeout);
870
871 // start timer
872 switch (timerType)
873 {
874 case WifiTxTimer::WAIT_NORMAL_ACK_AFTER_DL_MU_PPDU:
875 NS_ASSERT(mpdu);
876 m_txTimer.Set(timerType,
877 timeout,
878 staExpectResponseFrom,
879 &HeFrameExchangeManager::NormalAckTimeout,
880 this,
881 mpdu,
882 m_txParams.m_txVector);
883 break;
884 case WifiTxTimer::WAIT_BLOCK_ACK:
885 NS_ASSERT(psdu);
886 m_txTimer.Set(timerType,
887 timeout,
888 staExpectResponseFrom,
889 &HeFrameExchangeManager::BlockAckTimeout,
890 this,
891 psdu,
892 m_txParams.m_txVector);
893 break;
894 case WifiTxTimer::WAIT_BLOCK_ACKS_IN_TB_PPDU:
895 m_txTimer.Set(timerType,
896 timeout,
897 staExpectResponseFrom,
898 &HeFrameExchangeManager::BlockAcksInTbPpduTimeout,
899 this,
900 &m_psduMap,
901 staExpectResponseFrom.size());
902 break;
903 case WifiTxTimer::WAIT_TB_PPDU_AFTER_BASIC_TF:
904 case WifiTxTimer::WAIT_QOS_NULL_AFTER_BSRP_TF:
905 m_txTimer.Set(timerType,
906 timeout,
907 staExpectResponseFrom,
908 &HeFrameExchangeManager::TbPpduTimeout,
909 this,
910 &m_psduMap,
911 staExpectResponseFrom.size());
912 break;
913 case WifiTxTimer::WAIT_BLOCK_ACK_AFTER_TB_PPDU:
914 m_txTimer.Set(timerType,
915 timeout,
916 staExpectResponseFrom,
917 &HeFrameExchangeManager::BlockAckAfterTbPpduTimeout,
918 this,
919 m_psduMap.begin()->second,
920 m_txParams.m_txVector);
921 break;
922 default:
923 NS_ABORT_MSG("Unknown timer type: " << timerType);
924 break;
925 }
926 }
927
928 // transmit the map of PSDUs
929 ForwardPsduMapDown(psduMap, m_txParams.m_txVector);
930
931 if (timerType == WifiTxTimer::WAIT_BLOCK_ACKS_IN_TB_PPDU ||
932 timerType == WifiTxTimer::WAIT_TB_PPDU_AFTER_BASIC_TF ||
933 timerType == WifiTxTimer::WAIT_QOS_NULL_AFTER_BSRP_TF)
934 {
935 // Pass TRIGVECTOR to HE PHY (equivalent to PHY-TRIGGER.request primitive)
936 auto hePhy = StaticCast<HePhy>(m_phy->GetPhyEntity(responseTxVector->GetModulationClass()));
937 hePhy->SetTrigVector(m_trigVector, m_txTimer.GetDelayLeft());
938 }
939 else if (timerType == WifiTxTimer::NOT_RUNNING && m_txParams.m_txVector.IsUlMu())
940 {
941 // clear m_psduMap after sending QoS Null frames following a BSRP Trigger Frame
942 Simulator::Schedule(txDuration, &WifiPsduMap::clear, &m_psduMap);
943 }
944
945 if (m_txTimer.IsRunning() && timerType != WifiTxTimer::WAIT_BLOCK_ACK_AFTER_TB_PPDU)
946 {
947 NS_ASSERT(m_sentFrameTo.empty());
948
949 // do not record that a frame is being sent to an EMLSR client unless the AP is sending a
950 // BSRP TF or the EMLSR client is already protected
951 for (const auto& address : staExpectResponseFrom)
952 {
953 if (!GetWifiRemoteStationManager()->GetEmlsrEnabled(address) ||
954 timerType == WifiTxTimer::WAIT_QOS_NULL_AFTER_BSRP_TF ||
955 m_protectedStas.contains(address))
956 {
957 m_sentFrameTo.insert(address);
958 }
959 }
960 }
961}
962
963void
964HeFrameExchangeManager::ForwardPsduMapDown(WifiConstPsduMap psduMap, WifiTxVector& txVector)
965{
966 NS_LOG_FUNCTION(this << psduMap << txVector);
967
968 if (ns3::IsDlMu(txVector.GetPreambleType()))
969 {
970 auto hePhy = StaticCast<HePhy>(m_phy->GetPhyEntity(txVector.GetModulationClass()));
971 auto sigBMode = hePhy->GetSigBMode(txVector);
972 txVector.SetSigBMode(sigBMode);
973 }
974
975 for (const auto& psdu : psduMap)
976 {
977 NS_LOG_DEBUG("Transmitting: [STAID=" << psdu.first << ", " << *psdu.second << "]");
978 }
979 NS_LOG_DEBUG("TXVECTOR: " << txVector);
980 for (const auto& [staId, psdu] : psduMap)
981 {
982 FinalizeMacHeader(psdu);
983 NotifyTxToEdca(psdu);
984 }
985 m_allowedWidth = std::min(m_allowedWidth, txVector.GetChannelWidth());
986
987 if (psduMap.size() > 1 || psduMap.begin()->second->IsAggregate() ||
988 psduMap.begin()->second->IsSingle())
989 {
990 txVector.SetAggregation(true);
991 }
992
993 auto txDuration = WifiPhy::CalculateTxDuration(psduMap, txVector, m_phy->GetPhyBand());
994 // The TXNAV timer is a single timer, shared by the EDCAFs within a STA, that is initialized
995 // with the duration from the Duration/ID field in the frame most recently successfully
996 // transmitted by the TXOP holder, except for PS-Poll frames. (Sec.10.23.2.2 IEEE 802.11-2020)
997 m_txNav = Max(m_txNav, Simulator::Now() + txDuration + psduMap.cbegin()->second->GetDuration());
998
999 m_phy->Send(psduMap, txVector);
1000}
1001
1003HeFrameExchangeManager::PrepareMuBar(const WifiTxVector& responseTxVector,
1004 std::map<uint16_t, CtrlBAckRequestHeader> recipients) const
1005{
1006 NS_LOG_FUNCTION(this << responseTxVector);
1007 NS_ASSERT(responseTxVector.GetHeMuUserInfoMap().size() == recipients.size());
1008 NS_ASSERT(!recipients.empty());
1009
1010 CtrlTriggerHeader muBar(TriggerFrameType::MU_BAR_TRIGGER, responseTxVector);
1011 SetTargetRssi(muBar);
1012 // Set the CS Required subfield to true, unless the UL Length subfield is less
1013 // than or equal to 418 (see Section 26.5.2.5 of 802.11ax-2021)
1014 muBar.SetCsRequired(muBar.GetUlLength() > 418);
1015
1016 // Add the Trigger Dependent User Info subfield to every User Info field
1017 for (auto& userInfo : muBar)
1018 {
1019 auto recipientIt = recipients.find(userInfo.GetAid12());
1020 NS_ASSERT(recipientIt != recipients.end());
1021
1022 // Store the BAR in the Trigger Dependent User Info subfield
1023 userInfo.SetMuBarTriggerDepUserInfo(recipientIt->second);
1024 }
1025
1026 Ptr<Packet> bar = Create<Packet>();
1027 bar->AddHeader(muBar);
1028 Mac48Address rxAddress;
1029 // "If the Trigger frame has one User Info field and the AID12 subfield of the
1030 // User Info contains the AID of a STA, then the RA field is set to the address
1031 // of that STA". Otherwise, it is set to the broadcast address (Sec. 9.3.1.23 -
1032 // 802.11ax amendment draft 3.0)
1033 if (muBar.GetNUserInfoFields() > 1)
1034 {
1035 rxAddress = Mac48Address::GetBroadcast();
1036 }
1037 else
1038 {
1039 NS_ASSERT(m_apMac);
1040 rxAddress = m_apMac->GetStaList(m_linkId).at(recipients.begin()->first);
1041 }
1042
1043 WifiMacHeader hdr;
1045 hdr.SetAddr1(rxAddress);
1046 hdr.SetAddr2(m_self);
1047 hdr.SetDsNotTo();
1048 hdr.SetDsNotFrom();
1049 hdr.SetNoRetry();
1050 hdr.SetNoMoreFragments();
1051
1052 return Create<WifiMpdu>(bar, hdr);
1053}
1054
1055void
1056HeFrameExchangeManager::CalculateProtectionTime(WifiProtection* protection) const
1057{
1058 NS_LOG_FUNCTION(this << protection);
1059 NS_ASSERT(protection != nullptr);
1060
1061 if (protection->method == WifiProtection::MU_RTS_CTS)
1062 {
1063 auto muRtsCtsProtection = static_cast<WifiMuRtsCtsProtection*>(protection);
1064
1065 // Get the TXVECTOR used by one station to send the CTS response. This is used
1066 // to compute the TX duration, so it does not matter which station we choose
1067 WifiTxVector ctsTxVector =
1068 GetCtsTxVectorAfterMuRts(muRtsCtsProtection->muRts,
1069 muRtsCtsProtection->muRts.begin()->GetAid12());
1070
1072 muRtsCtsProtection->muRts.GetSerializedSize() + WIFI_MAC_FCS_LENGTH;
1073 muRtsCtsProtection->protectionTime =
1074 WifiPhy::CalculateTxDuration(muRtsSize,
1075 muRtsCtsProtection->muRtsTxVector,
1076 m_phy->GetPhyBand()) +
1077 WifiPhy::CalculateTxDuration(GetCtsSize(), ctsTxVector, m_phy->GetPhyBand()) +
1078 2 * m_phy->GetSifs();
1079 }
1080 else
1081 {
1082 VhtFrameExchangeManager::CalculateProtectionTime(protection);
1083 }
1084}
1085
1086void
1087HeFrameExchangeManager::CalculateAcknowledgmentTime(WifiAcknowledgment* acknowledgment) const
1088{
1089 NS_LOG_FUNCTION(this << acknowledgment);
1090 NS_ASSERT(acknowledgment);
1091
1092 /*
1093 * Acknowledgment via a sequence of BlockAckReq and BlockAck frames
1094 */
1095 if (acknowledgment->method == WifiAcknowledgment::DL_MU_BAR_BA_SEQUENCE)
1096 {
1097 auto dlMuBarBaAcknowledgment = static_cast<WifiDlMuBarBaSequence*>(acknowledgment);
1098
1099 Time duration;
1100
1101 // normal ack or implicit BAR policy can be used for (no more than) one receiver
1102 NS_ABORT_IF(dlMuBarBaAcknowledgment->stationsReplyingWithNormalAck.size() +
1103 dlMuBarBaAcknowledgment->stationsReplyingWithBlockAck.size() >
1104 1);
1105
1106 if (!dlMuBarBaAcknowledgment->stationsReplyingWithNormalAck.empty())
1107 {
1108 const auto& info =
1109 dlMuBarBaAcknowledgment->stationsReplyingWithNormalAck.begin()->second;
1110 duration +=
1111 m_phy->GetSifs() +
1112 WifiPhy::CalculateTxDuration(GetAckSize(), info.ackTxVector, m_phy->GetPhyBand());
1113 }
1114
1115 if (!dlMuBarBaAcknowledgment->stationsReplyingWithBlockAck.empty())
1116 {
1117 const auto& info =
1118 dlMuBarBaAcknowledgment->stationsReplyingWithBlockAck.begin()->second;
1119 duration +=
1120 m_phy->GetSifs() + WifiPhy::CalculateTxDuration(GetBlockAckSize(info.baType),
1121 info.blockAckTxVector,
1122 m_phy->GetPhyBand());
1123 }
1124
1125 for (const auto& stations : dlMuBarBaAcknowledgment->stationsSendBlockAckReqTo)
1126 {
1127 const auto& info = stations.second;
1128 duration += m_phy->GetSifs() +
1129 WifiPhy::CalculateTxDuration(GetBlockAckRequestSize(info.barType),
1130 info.blockAckReqTxVector,
1131 m_phy->GetPhyBand()) +
1132 m_phy->GetSifs() +
1133 WifiPhy::CalculateTxDuration(GetBlockAckSize(info.baType),
1134 info.blockAckTxVector,
1135 m_phy->GetPhyBand());
1136 }
1137
1138 dlMuBarBaAcknowledgment->acknowledgmentTime = duration;
1139 }
1140 /*
1141 * Acknowledgment via a MU-BAR Trigger Frame sent as single user frame
1142 */
1143 else if (acknowledgment->method == WifiAcknowledgment::DL_MU_TF_MU_BAR)
1144 {
1145 auto dlMuTfMuBarAcknowledgment = static_cast<WifiDlMuTfMuBar*>(acknowledgment);
1146
1147 Time duration;
1148
1149 for (const auto& stations : dlMuTfMuBarAcknowledgment->stationsReplyingWithBlockAck)
1150 {
1151 // compute the TX duration of the BlockAck response from this receiver.
1152 const auto& info = stations.second;
1153 NS_ASSERT(info.blockAckTxVector.GetHeMuUserInfoMap().size() == 1);
1154 uint16_t staId = info.blockAckTxVector.GetHeMuUserInfoMap().begin()->first;
1155 Time currBlockAckDuration = WifiPhy::CalculateTxDuration(GetBlockAckSize(info.baType),
1156 info.blockAckTxVector,
1157 m_phy->GetPhyBand(),
1158 staId);
1159 // update the max duration among all the Block Ack responses
1160 if (currBlockAckDuration > duration)
1161 {
1162 duration = currBlockAckDuration;
1163 }
1164 }
1165
1166 // The computed duration may not be coded exactly in the L-SIG length, hence determine
1167 // the exact duration corresponding to the value that will be coded in this field.
1168 WifiTxVector& txVector = dlMuTfMuBarAcknowledgment->stationsReplyingWithBlockAck.begin()
1169 ->second.blockAckTxVector;
1170 std::tie(dlMuTfMuBarAcknowledgment->ulLength, duration) =
1171 HePhy::ConvertHeTbPpduDurationToLSigLength(duration, txVector, m_phy->GetPhyBand());
1172
1173 uint32_t muBarSize = GetMuBarSize(dlMuTfMuBarAcknowledgment->barTypes);
1174 if (dlMuTfMuBarAcknowledgment->muBarTxVector.GetModulationClass() >= WIFI_MOD_CLASS_VHT)
1175 {
1176 // MU-BAR TF will be sent as an S-MPDU
1177 muBarSize = MpduAggregator::GetSizeIfAggregated(muBarSize, 0);
1178 }
1179 dlMuTfMuBarAcknowledgment->acknowledgmentTime =
1180 m_phy->GetSifs() +
1181 WifiPhy::CalculateTxDuration(muBarSize,
1182 dlMuTfMuBarAcknowledgment->muBarTxVector,
1183 m_phy->GetPhyBand()) +
1184 m_phy->GetSifs() + duration;
1185 }
1186 /*
1187 * Acknowledgment requested by MU-BAR TFs aggregated to PSDUs in the DL MU PPDU
1188 */
1189 else if (acknowledgment->method == WifiAcknowledgment::DL_MU_AGGREGATE_TF)
1190 {
1191 auto dlMuAggrTfAcknowledgment = static_cast<WifiDlMuAggregateTf*>(acknowledgment);
1192
1193 Time duration;
1194
1195 for (const auto& stations : dlMuAggrTfAcknowledgment->stationsReplyingWithBlockAck)
1196 {
1197 // compute the TX duration of the BlockAck response from this receiver.
1198 const auto& info = stations.second;
1199 NS_ASSERT(info.blockAckTxVector.GetHeMuUserInfoMap().size() == 1);
1200 uint16_t staId = info.blockAckTxVector.GetHeMuUserInfoMap().begin()->first;
1201 Time currBlockAckDuration = WifiPhy::CalculateTxDuration(GetBlockAckSize(info.baType),
1202 info.blockAckTxVector,
1203 m_phy->GetPhyBand(),
1204 staId);
1205 // update the max duration among all the Block Ack responses
1206 if (currBlockAckDuration > duration)
1207 {
1208 duration = currBlockAckDuration;
1209 }
1210 }
1211
1212 // The computed duration may not be coded exactly in the L-SIG length, hence determine
1213 // the exact duration corresponding to the value that will be coded in this field.
1214 WifiTxVector& txVector =
1215 dlMuAggrTfAcknowledgment->stationsReplyingWithBlockAck.begin()->second.blockAckTxVector;
1216 std::tie(dlMuAggrTfAcknowledgment->ulLength, duration) =
1217 HePhy::ConvertHeTbPpduDurationToLSigLength(duration, txVector, m_phy->GetPhyBand());
1218 dlMuAggrTfAcknowledgment->acknowledgmentTime = m_phy->GetSifs() + duration;
1219 }
1220 /*
1221 * Basic Trigger Frame starting an UL MU transmission
1222 */
1223 else if (acknowledgment->method == WifiAcknowledgment::UL_MU_MULTI_STA_BA)
1224 {
1225 auto ulMuMultiStaBa = static_cast<WifiUlMuMultiStaBa*>(acknowledgment);
1226
1227 Time duration = WifiPhy::CalculateTxDuration(GetBlockAckSize(ulMuMultiStaBa->baType),
1228 ulMuMultiStaBa->multiStaBaTxVector,
1229 m_phy->GetPhyBand());
1230 ulMuMultiStaBa->acknowledgmentTime = m_phy->GetSifs() + duration;
1231 }
1232 /*
1233 * TB PPDU solicired by a Basic or BSRP Trigger Frame
1234 */
1235 else if (acknowledgment->method == WifiAcknowledgment::ACK_AFTER_TB_PPDU)
1236 {
1237 // The station solicited by the Trigger Frame does not have to account
1238 // for the actual acknowledgment time since it is given the PPDU duration
1239 // through the Trigger Frame
1240 acknowledgment->acknowledgmentTime = Seconds(0);
1241 }
1242 else
1243 {
1244 VhtFrameExchangeManager::CalculateAcknowledgmentTime(acknowledgment);
1245 }
1246}
1247
1249HeFrameExchangeManager::GetCtsModeAfterMuRts() const
1250{
1251 // The CTS frame sent in response to an MU-RTS Trigger frame shall be carried in a non-HT or
1252 // non-HT duplicate PPDU (see Clause 17) with a 6 Mb/s rate (Sec. 26.2.6.3 of 802.11ax-2021)
1253 return m_phy->GetPhyBand() == WIFI_PHY_BAND_2_4GHZ ? ErpOfdmPhy::GetErpOfdmRate6Mbps()
1254 : OfdmPhy::GetOfdmRate6Mbps();
1255}
1256
1258HeFrameExchangeManager::GetCtsTxVectorAfterMuRts(const CtrlTriggerHeader& trigger,
1259 uint16_t staId) const
1260{
1261 NS_LOG_FUNCTION(this << trigger << staId);
1262
1263 auto userInfoIt = trigger.FindUserInfoWithAid(staId);
1264 NS_ASSERT_MSG(userInfoIt != trigger.end(), "User Info field for AID=" << staId << " not found");
1265 MHz_u bw = 0;
1266
1267 if (uint8_t ru = userInfoIt->GetMuRtsRuAllocation(); ru < 65)
1268 {
1269 bw = 20;
1270 }
1271 else if (ru < 67)
1272 {
1273 bw = 40;
1274 }
1275 else if (ru == 67)
1276 {
1277 bw = 80;
1278 }
1279 else
1280 {
1281 NS_ASSERT(ru == 68);
1282 bw = 160;
1283 }
1284
1285 auto txVector = GetWifiRemoteStationManager()->GetCtsTxVector(m_bssid, GetCtsModeAfterMuRts());
1286 // set the channel width of the CTS TXVECTOR according to the allocated RU
1287 txVector.SetChannelWidth(bw);
1288
1289 return txVector;
1290}
1291
1292Time
1293HeFrameExchangeManager::GetTxDuration(uint32_t ppduPayloadSize,
1294 Mac48Address receiver,
1295 const WifiTxParameters& txParams) const
1296{
1297 if (!txParams.m_txVector.IsMu())
1298 {
1299 return VhtFrameExchangeManager::GetTxDuration(ppduPayloadSize, receiver, txParams);
1300 }
1301
1302 NS_ASSERT_MSG(!txParams.m_txVector.IsDlMu() || m_apMac, "DL MU can be done by an AP");
1303 NS_ASSERT_MSG(!txParams.m_txVector.IsUlMu() || m_staMac, "UL MU can be done by a STA");
1304
1305 if (txParams.m_acknowledgment &&
1306 txParams.m_acknowledgment->method == WifiAcknowledgment::DL_MU_AGGREGATE_TF)
1307 {
1308 // we need to account for the size of the aggregated MU-BAR Trigger Frame
1309 auto psduInfo = txParams.GetPsduInfo(receiver);
1310 NS_ASSERT_MSG(psduInfo, "No information for " << receiver << " in TX params");
1311 NS_ASSERT_MSG(!psduInfo->seqNumbers.empty(), "No sequence number for " << receiver);
1312 const auto tid = psduInfo->seqNumbers.cbegin()->first;
1313
1314 ppduPayloadSize = MpduAggregator::GetSizeIfAggregated(
1315 GetMuBarSize({m_mac->GetBarTypeAsOriginator(receiver, tid)}),
1316 ppduPayloadSize);
1317 }
1318
1319 uint16_t staId = (txParams.m_txVector.IsDlMu() ? m_apMac->GetAssociationId(receiver, m_linkId)
1320 : m_staMac->GetAssociationId());
1321 Time psduDuration = WifiPhy::CalculateTxDuration(ppduPayloadSize,
1322 txParams.m_txVector,
1323 m_phy->GetPhyBand(),
1324 staId);
1325
1326 return txParams.m_txDuration ? std::max(psduDuration, *txParams.m_txDuration) : psduDuration;
1327}
1328
1329void
1330HeFrameExchangeManager::TbPpduTimeout(WifiPsduMap* psduMap, std::size_t nSolicitedStations)
1331{
1332 NS_LOG_FUNCTION(this << psduMap << nSolicitedStations);
1333 DoTbPpduTimeout(psduMap, nSolicitedStations, true);
1334}
1335
1336void
1337HeFrameExchangeManager::DoTbPpduTimeout(WifiPsduMap* psduMap,
1338 std::size_t nSolicitedStations,
1339 bool updateFailedCw)
1340{
1341 const auto& staMissedTbPpduFrom = m_txTimer.GetStasExpectedToRespond();
1342 NS_LOG_FUNCTION(this << psduMap << staMissedTbPpduFrom.size() << nSolicitedStations
1343 << updateFailedCw);
1344
1345 NS_ASSERT(psduMap);
1346 NS_ASSERT(IsTrigger(*psduMap));
1347
1348 // This method is called if some station(s) did not send a TB PPDU
1349 NS_ASSERT(!staMissedTbPpduFrom.empty());
1350 NS_ASSERT(m_edca);
1351
1352 if (staMissedTbPpduFrom.size() == nSolicitedStations)
1353 {
1354 // no station replied, the transmission failed
1355 CtrlTriggerHeader trigger;
1356 psduMap->cbegin()->second->GetPayload(0)->PeekHeader(trigger);
1357
1358 if (m_continueTxopAfterBsrpTf && m_edca->GetTxopLimit(m_linkId).IsZero() &&
1359 trigger.IsBsrp())
1360 {
1361 SendCfEndIfNeeded();
1362 }
1363
1364 TransmissionFailed(!updateFailedCw);
1365 }
1366 else if (!m_multiStaBaEvent.IsPending())
1367 {
1368 m_edca->ResetCw(m_linkId);
1369 TransmissionSucceeded();
1370 }
1371 else
1372 {
1373 // Stations that did not respond must be removed from the set of stations for which
1374 // protection is not needed in the current TXOP.
1375 for (const auto& address : staMissedTbPpduFrom)
1376 {
1377 NS_LOG_DEBUG(address << " did not respond, hence it is no longer protected");
1378 m_protectedStas.erase(address);
1379 m_sentFrameTo.erase(address);
1380 }
1381 if (m_protectedIfResponded)
1382 {
1383 m_protectedStas.merge(m_sentFrameTo);
1384 }
1385 m_sentFrameTo.clear();
1386 }
1387
1388 m_psduMap.clear();
1389}
1390
1391void
1392HeFrameExchangeManager::BlockAcksInTbPpduTimeout(WifiPsduMap* psduMap,
1393 std::size_t nSolicitedStations)
1394{
1395 NS_LOG_FUNCTION(this << psduMap << nSolicitedStations);
1396
1397 NS_ASSERT(psduMap);
1398 NS_ASSERT(m_txParams.m_acknowledgment &&
1399 (m_txParams.m_acknowledgment->method == WifiAcknowledgment::DL_MU_AGGREGATE_TF ||
1400 m_txParams.m_acknowledgment->method == WifiAcknowledgment::DL_MU_TF_MU_BAR));
1401
1402 // This method is called if some station(s) did not send a BlockAck frame in a TB PPDU
1403 const auto& staMissedBlockAckFrom = m_txTimer.GetStasExpectedToRespond();
1404 NS_ASSERT(!staMissedBlockAckFrom.empty());
1405
1406 if (staMissedBlockAckFrom.size() == nSolicitedStations)
1407 {
1408 // no station replied, the transmission failed
1409 GetWifiRemoteStationManager()->ReportDataFailed(*psduMap->begin()->second->begin());
1410 }
1411
1412 if (m_triggerFrame)
1413 {
1414 // this is strictly needed for DL_MU_TF_MU_BAR only
1415 m_triggerFrame = nullptr;
1416 }
1417
1418 for (const auto& sta : staMissedBlockAckFrom)
1419 {
1420 auto psdu = GetPsduTo(sta, *psduMap);
1421 NS_ASSERT(psdu);
1422 MissedBlockAck(psdu, m_txParams.m_txVector);
1423 }
1424
1425 NS_ASSERT(m_edca);
1426
1427 if (staMissedBlockAckFrom.size() == nSolicitedStations)
1428 {
1429 // no station replied, the transmission failed
1430 TransmissionFailed();
1431 }
1432 else
1433 {
1434 m_edca->ResetCw(m_linkId);
1435 TransmissionSucceeded();
1436 }
1437 m_psduMap.clear();
1438}
1439
1440void
1441HeFrameExchangeManager::BlockAckAfterTbPpduTimeout(Ptr<WifiPsdu> psdu, const WifiTxVector& txVector)
1442{
1443 NS_LOG_FUNCTION(this << *psdu << txVector);
1444
1445 GetWifiRemoteStationManager()->ReportDataFailed(*psdu->begin());
1446
1447 MissedBlockAck(psdu, m_txParams.m_txVector);
1448
1449 // This is a PSDU sent in a TB PPDU. An HE STA resumes the EDCA backoff procedure
1450 // without modifying CW or the backoff counter for the associated EDCAF, after
1451 // transmission of an MPDU in a TB PPDU regardless of whether the STA has received
1452 // the corresponding acknowledgment frame in response to the MPDU sent in the TB PPDU
1453 // (Sec. 10.22.2.2 of 11ax Draft 3.0)
1454 m_psduMap.clear();
1455}
1456
1457void
1458HeFrameExchangeManager::NormalAckTimeout(Ptr<WifiMpdu> mpdu, const WifiTxVector& txVector)
1459{
1460 NS_LOG_FUNCTION(this << *mpdu << txVector);
1461
1462 VhtFrameExchangeManager::NormalAckTimeout(mpdu, txVector);
1463
1464 // If a Normal Ack is missed in response to a DL MU PPDU requiring acknowledgment
1465 // in SU format, we have to set the Retry flag for all transmitted MPDUs that have
1466 // not been acknowledged nor discarded and clear m_psduMap since the transmission failed.
1467 for (auto& psdu : m_psduMap)
1468 {
1469 for (auto& mpdu : *PeekPointer(psdu.second))
1470 {
1471 if (mpdu->IsQueued())
1472 {
1473 m_mac->GetTxopQueue(mpdu->GetQueueAc())->GetOriginal(mpdu)->GetHeader().SetRetry();
1474 mpdu->ResetInFlight(m_linkId);
1475 }
1476 }
1477 }
1478 m_psduMap.clear();
1479}
1480
1481void
1482HeFrameExchangeManager::BlockAckTimeout(Ptr<WifiPsdu> psdu, const WifiTxVector& txVector)
1483{
1484 NS_LOG_FUNCTION(this << *psdu << txVector);
1485
1486 VhtFrameExchangeManager::BlockAckTimeout(psdu, txVector);
1487
1488 // If a Block Ack is missed in response to a DL MU PPDU requiring acknowledgment
1489 // in SU format, we have to set the Retry flag for all transmitted MPDUs that have
1490 // not been acknowledged nor discarded and clear m_psduMap since the transmission failed.
1491 for (auto& psdu : m_psduMap)
1492 {
1493 for (auto& mpdu : *PeekPointer(psdu.second))
1494 {
1495 if (mpdu->IsQueued())
1496 {
1497 mpdu->GetHeader().SetRetry();
1498 }
1499 }
1500 }
1501 m_psduMap.clear();
1502}
1503
1505HeFrameExchangeManager::GetTrigVector(const CtrlTriggerHeader& trigger) const
1506{
1507 WifiTxVector v;
1508 v.SetPreambleType(trigger.GetVariant() == TriggerFrameVariant::HE ? WIFI_PREAMBLE_HE_TB
1510 v.SetChannelWidth(trigger.GetUlBandwidth());
1512 v.SetLength(trigger.GetUlLength());
1513 for (const auto& userInfoField : trigger)
1514 {
1516 userInfoField.GetAid12(),
1517 {userInfoField.GetRuAllocation(), userInfoField.GetUlMcs(), userInfoField.GetNss()});
1518 }
1519 return v;
1520}
1521
1523HeFrameExchangeManager::GetHeTbTxVector(CtrlTriggerHeader trigger, Mac48Address triggerSender) const
1524{
1525 NS_ASSERT(triggerSender !=
1526 m_self); // TxPower information is used only by STAs, it is useless for the sending AP
1527 // (which can directly use CtrlTriggerHeader::GetHeTbTxVector)
1528 NS_ASSERT(m_staMac);
1529 uint16_t staId = m_staMac->GetAssociationId();
1530 auto userInfoIt = trigger.FindUserInfoWithAid(staId);
1531 NS_ASSERT(userInfoIt != trigger.end());
1532
1533 WifiTxVector v = trigger.GetHeTbTxVector(staId);
1534
1535 Ptr<HeConfiguration> heConfiguration = m_mac->GetHeConfiguration();
1536 NS_ASSERT_MSG(heConfiguration, "This STA has to be an HE station to send an HE TB PPDU");
1537 v.SetBssColor(heConfiguration->m_bssColor);
1538
1539 if (userInfoIt->IsUlTargetRssiMaxTxPower())
1540 {
1541 NS_LOG_LOGIC("AP requested using the max transmit power (" << m_phy->GetTxPowerEnd()
1542 << " dBm)");
1543 v.SetTxPowerLevel(m_phy->GetNTxPower());
1544 return v;
1545 }
1546
1547 uint8_t powerLevel = GetWifiRemoteStationManager()->GetDefaultTxPowerLevel();
1548 /**
1549 * Get the transmit power to use for an HE TB PPDU
1550 * considering:
1551 * - the transmit power used by the AP to send the Trigger Frame (TF),
1552 * obtained from the AP TX Power subfield of the Common Info field
1553 * of the TF.
1554 * - the target uplink RSSI expected by the AP for the triggered HE TB PPDU,
1555 * obtained from the UL Target RSSI subfield of the User Info field
1556 * of the TF.
1557 * - the RSSI of the PPDU containing the TF, typically logged by the
1558 * WifiRemoteStationManager upon reception of the TF from the AP.
1559 *
1560 * It is assumed that path loss is symmetric (i.e. uplink path loss is
1561 * equivalent to the measured downlink path loss);
1562 *
1563 * Refer to section 27.3.14.2 (Power pre-correction) of 802.11ax Draft 4.0 for more details.
1564 */
1565 auto optRssi = GetMostRecentRssi(triggerSender);
1566 NS_ASSERT(optRssi);
1567 int8_t pathLossDb =
1568 trigger.GetApTxPower() -
1569 static_cast<int8_t>(
1570 *optRssi); // cast RSSI to be on equal footing with AP Tx power information
1571 auto reqTxPower = static_cast<dBm_u>(userInfoIt->GetUlTargetRssi() + pathLossDb);
1572
1573 // Convert the transmit power to a power level
1574 uint8_t numPowerLevels = m_phy->GetNTxPower();
1575 if (numPowerLevels > 1)
1576 {
1577 dBm_u step = (m_phy->GetTxPowerEnd() - m_phy->GetTxPowerStart()) / (numPowerLevels - 1);
1578 powerLevel = static_cast<uint8_t>(
1579 ceil((reqTxPower - m_phy->GetTxPowerStart()) /
1580 step)); // better be slightly above so as to satisfy target UL RSSI
1581 if (powerLevel > numPowerLevels)
1582 {
1583 powerLevel = numPowerLevels; // capping will trigger warning below
1584 }
1585 }
1586 if (reqTxPower > m_phy->GetPower(powerLevel))
1587 {
1588 NS_LOG_WARN("The requested power level (" << reqTxPower << "dBm) cannot be satisfied (max: "
1589 << m_phy->GetTxPowerEnd() << "dBm)");
1590 }
1591 v.SetTxPowerLevel(powerLevel);
1592 NS_LOG_LOGIC("UL power control: "
1593 << "input {pathLoss=" << pathLossDb << "dB, reqTxPower=" << reqTxPower << "dBm}"
1594 << " output {powerLevel=" << +powerLevel << " -> " << m_phy->GetPower(powerLevel)
1595 << "dBm}"
1596 << " PHY power capa {min=" << m_phy->GetTxPowerStart() << "dBm, max="
1597 << m_phy->GetTxPowerEnd() << "dBm, levels:" << +numPowerLevels << "}");
1598
1599 return v;
1600}
1601
1602std::optional<dBm_u>
1603HeFrameExchangeManager::GetMostRecentRssi(const Mac48Address& address) const
1604{
1605 return GetWifiRemoteStationManager()->GetMostRecentRssi(address);
1606}
1607
1608void
1609HeFrameExchangeManager::SetTargetRssi(CtrlTriggerHeader& trigger) const
1610{
1611 NS_LOG_FUNCTION(this);
1612 NS_ASSERT(m_apMac);
1613
1614 trigger.SetApTxPower(static_cast<int8_t>(
1615 m_phy->GetPower(GetWifiRemoteStationManager()->GetDefaultTxPowerLevel())));
1616 for (auto& userInfo : trigger)
1617 {
1618 const auto staList = m_apMac->GetStaList(m_linkId);
1619 auto itAidAddr = staList.find(userInfo.GetAid12());
1620 NS_ASSERT(itAidAddr != staList.end());
1621 auto optRssi = GetMostRecentRssi(itAidAddr->second);
1622 NS_ASSERT(optRssi);
1623 auto rssi = static_cast<int8_t>(*optRssi);
1624 rssi = (rssi >= -20)
1625 ? -20
1626 : ((rssi <= -110) ? -110 : rssi); // cap so as to keep within [-110; -20] dBm
1627 userInfo.SetUlTargetRssi(rssi);
1628 }
1629}
1630
1631void
1632HeFrameExchangeManager::PostProcessFrame(Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector)
1633{
1634 NS_LOG_FUNCTION(this << psdu << txVector);
1635
1636 auto txVectorCopy = txVector;
1637
1638 if (psdu->GetNMpdus() == 1 && psdu->GetHeader(0).IsTrigger())
1639 {
1640 CtrlTriggerHeader trigger;
1641 psdu->GetPayload(0)->PeekHeader(trigger);
1642 if (trigger.IsMuRts())
1643 {
1644 const WifiMacHeader& muRts = psdu->GetHeader(0);
1645 // A station receiving an MU-RTS behaves just like as if it received an RTS.
1646 // Determine whether the MU-RTS is addressed to this station or not and
1647 // prepare an "equivalent" RTS frame so that we can reuse the UpdateNav()
1648 // and SetTxopHolder() methods of the parent classes
1649 WifiMacHeader rts;
1651 rts.SetDsNotFrom();
1652 rts.SetDsNotTo();
1653 rts.SetDuration(muRts.GetDuration());
1654 rts.SetAddr2(muRts.GetAddr2());
1655 if (m_staMac != nullptr && m_staMac->IsAssociated() &&
1656 muRts.GetAddr2() == m_bssid // sent by the AP this STA is associated with
1657 && trigger.FindUserInfoWithAid(m_staMac->GetAssociationId()) != trigger.end())
1658 {
1659 // the MU-RTS is addressed to this station
1660 rts.SetAddr1(m_self);
1661 }
1662 else
1663 {
1664 rts.SetAddr1(muRts.GetAddr2()); // an address different from that of this station
1665 }
1666 psdu = Create<const WifiPsdu>(Create<Packet>(), rts);
1667 // The duration of the NAV reset timeout has to take into account that the CTS
1668 // response is sent using the 6 Mbps data rate
1669 txVectorCopy =
1670 GetWifiRemoteStationManager()->GetCtsTxVector(m_bssid, GetCtsModeAfterMuRts());
1671 }
1672 }
1673 VhtFrameExchangeManager::PostProcessFrame(psdu, txVectorCopy);
1674}
1675
1676void
1677HeFrameExchangeManager::SendCtsAfterMuRts(const WifiMacHeader& muRtsHdr,
1678 const CtrlTriggerHeader& trigger,
1679 double muRtsSnr)
1680{
1681 NS_LOG_FUNCTION(this << muRtsHdr << trigger << muRtsSnr);
1682
1683 if (!UlMuCsMediumIdle(trigger))
1684 {
1685 NS_LOG_DEBUG("UL MU CS indicated medium busy, cannot send CTS");
1686 return;
1687 }
1688
1689 NS_ASSERT(m_staMac != nullptr && m_staMac->IsAssociated());
1690 WifiTxVector ctsTxVector = GetCtsTxVectorAfterMuRts(trigger, m_staMac->GetAssociationId());
1691 ctsTxVector.SetTriggerResponding(true);
1692
1693 DoSendCtsAfterRts(muRtsHdr, ctsTxVector, muRtsSnr);
1694}
1695
1696void
1697HeFrameExchangeManager::SendMultiStaBlockAck(const WifiTxParameters& txParams, Time durationId)
1698{
1699 NS_LOG_FUNCTION(this << &txParams << durationId.As(Time::US));
1700
1701 NS_ASSERT(m_apMac);
1702 NS_ASSERT(txParams.m_acknowledgment &&
1703 txParams.m_acknowledgment->method == WifiAcknowledgment::UL_MU_MULTI_STA_BA);
1704 auto acknowledgment = static_cast<WifiUlMuMultiStaBa*>(txParams.m_acknowledgment.get());
1705
1706 NS_ASSERT(!acknowledgment->stationsReceivingMultiStaBa.empty());
1707
1708 CtrlBAckResponseHeader blockAck;
1709 blockAck.SetType(acknowledgment->baType);
1710
1711 Mac48Address receiver;
1712
1713 for (const auto& staInfo : acknowledgment->stationsReceivingMultiStaBa)
1714 {
1715 receiver = staInfo.first.first;
1716 uint8_t tid = staInfo.first.second;
1717 std::size_t index = staInfo.second;
1718
1719 blockAck.SetAid11(m_apMac->GetAssociationId(receiver, m_linkId), index);
1720 blockAck.SetTidInfo(tid, index);
1721
1722 if (tid == 14)
1723 {
1724 // All-ack context
1725 NS_LOG_DEBUG("Multi-STA Block Ack: Sending All-ack to=" << receiver);
1726 blockAck.SetAckType(true, index);
1727 continue;
1728 }
1729
1730 if (acknowledgment->baType.m_bitmapLen.at(index) == 0)
1731 {
1732 // Acknowledgment context
1733 NS_LOG_DEBUG("Multi-STA Block Ack: Sending Ack to=" << receiver);
1734 blockAck.SetAckType(true, index);
1735 }
1736 else
1737 {
1738 // Block acknowledgment context
1739 blockAck.SetAckType(false, index);
1740
1741 auto agreement = m_mac->GetBaAgreementEstablishedAsRecipient(receiver, tid);
1742 NS_ASSERT(agreement);
1743 agreement->get().FillBlockAckBitmap(&blockAck, index);
1744 NS_LOG_DEBUG("Multi-STA Block Ack: Sending Block Ack with seq="
1745 << blockAck.GetStartingSequence(index) << " to=" << receiver
1746 << " tid=" << +tid);
1747 }
1748 }
1749
1750 WifiMacHeader hdr;
1752 hdr.SetAddr1(acknowledgment->stationsReceivingMultiStaBa.size() == 1
1753 ? receiver
1754 : Mac48Address::GetBroadcast());
1755 hdr.SetAddr2(m_self);
1756 hdr.SetDsNotFrom();
1757 hdr.SetDsNotTo();
1758
1759 Ptr<Packet> packet = Create<Packet>();
1760 packet->AddHeader(blockAck);
1761 Ptr<WifiPsdu> psdu =
1762 GetWifiPsdu(Create<WifiMpdu>(packet, hdr), acknowledgment->multiStaBaTxVector);
1763
1764 Time txDuration = WifiPhy::CalculateTxDuration(GetBlockAckSize(acknowledgment->baType),
1765 acknowledgment->multiStaBaTxVector,
1766 m_phy->GetPhyBand());
1767 /**
1768 * In a BlockAck frame transmitted in response to a frame carried in HE TB PPDU under
1769 * single protection settings, the Duration/ID field is set to the value obtained from
1770 * the Duration/ID field of the frame that elicited the response minus the time, in
1771 * microseconds between the end of the PPDU carrying the frame that elicited the response
1772 * and the end of the PPDU carrying the BlockAck frame.
1773 * Under multiple protection settings, the Duration/ID field in a BlockAck frame transmitted
1774 * in response to a frame carried in HE TB PPDU is set according to the multiple protection
1775 * settings defined in 9.2.5.2. (Sec. 9.2.5.7 of 802.11ax-2021)
1776 */
1777 NS_ASSERT(m_edca);
1778 if (m_edca->GetTxopLimit(m_linkId).IsZero())
1779 {
1780 // single protection settings
1781 psdu->SetDuration(Max(durationId - m_phy->GetSifs() - txDuration, Seconds(0)));
1782 }
1783 else
1784 {
1785 // multiple protection settings
1786 psdu->SetDuration(Max(m_edca->GetRemainingTxop(m_linkId) - txDuration, Seconds(0)));
1787 }
1788
1789 psdu->GetPayload(0)->AddPacketTag(m_muSnrTag);
1790
1791 ForwardPsduDown(psdu, acknowledgment->multiStaBaTxVector);
1792
1793 // continue with the TXOP if time remains
1794 m_psduMap.clear();
1795 m_edca->ResetCw(m_linkId);
1796 m_muSnrTag.Reset();
1797 Simulator::Schedule(txDuration, &HeFrameExchangeManager::TransmissionSucceeded, this);
1798}
1799
1800void
1801HeFrameExchangeManager::ReceiveBasicTrigger(const CtrlTriggerHeader& trigger,
1802 const WifiMacHeader& hdr)
1803{
1804 NS_LOG_FUNCTION(this << trigger << hdr);
1805 NS_ASSERT(trigger.IsBasic());
1806 NS_ASSERT(m_staMac && m_staMac->IsAssociated());
1807
1808 NS_LOG_DEBUG("Received a Trigger Frame (basic variant) soliciting a transmission");
1809
1810 if (!UlMuCsMediumIdle(trigger))
1811 {
1812 return;
1813 }
1814
1815 // Starting from the Preferred AC indicated in the Trigger Frame, check if there
1816 // is either a pending BlockAckReq frame or a data frame that can be transmitted
1817 // in the allocated time and is addressed to a station with which a Block Ack
1818 // agreement has been established.
1819
1820 // create the sequence of TIDs to check
1821 std::vector<uint8_t> tids;
1822 uint16_t staId = m_staMac->GetAssociationId();
1823 AcIndex preferredAc = trigger.FindUserInfoWithAid(staId)->GetPreferredAc();
1824 auto acIt = wifiAcList.find(preferredAc);
1825 for (uint8_t i = 0; i < 4; i++)
1826 {
1827 NS_ASSERT(acIt != wifiAcList.end());
1828 tids.push_back(acIt->second.GetHighTid());
1829 tids.push_back(acIt->second.GetLowTid());
1830
1831 acIt++;
1832 if (acIt == wifiAcList.end())
1833 {
1834 acIt = wifiAcList.begin();
1835 }
1836 }
1837
1838 Ptr<WifiPsdu> psdu;
1839 WifiTxParameters txParams;
1840 WifiTxVector tbTxVector = GetHeTbTxVector(trigger, hdr.GetAddr2());
1841 Time ppduDuration = HePhy::ConvertLSigLengthToHeTbPpduDuration(trigger.GetUlLength(),
1842 tbTxVector,
1843 m_phy->GetPhyBand());
1844
1845 for (const auto& tid : tids)
1846 {
1847 Ptr<QosTxop> edca = m_mac->GetQosTxop(tid);
1848
1849 if (!m_mac->GetBaAgreementEstablishedAsOriginator(hdr.GetAddr2(), tid))
1850 {
1851 // no Block Ack agreement established for this TID
1852 continue;
1853 }
1854
1855 txParams.Clear();
1856 txParams.m_txVector = tbTxVector;
1857
1858 // first, check if there is a pending BlockAckReq frame
1859 if (auto mpdu = GetBar(edca->GetAccessCategory(), tid, hdr.GetAddr2());
1860 mpdu && TryAddMpdu(mpdu, txParams, ppduDuration))
1861 {
1862 NS_LOG_DEBUG("Sending a BAR within a TB PPDU");
1863 psdu = Create<WifiPsdu>(mpdu, true);
1864 break;
1865 }
1866
1867 // otherwise, check if a suitable data frame is available
1868 auto receiver =
1869 GetWifiRemoteStationManager()->GetMldAddress(hdr.GetAddr2()).value_or(hdr.GetAddr2());
1870 if (auto mpdu = edca->PeekNextMpdu(m_linkId, tid, receiver))
1871 {
1872 mpdu = CreateAliasIfNeeded(mpdu);
1873 if (auto item = edca->GetNextMpdu(m_linkId, mpdu, txParams, ppduDuration, false))
1874 {
1875 // try A-MPDU aggregation
1876 std::vector<Ptr<WifiMpdu>> mpduList =
1877 m_mpduAggregator->GetNextAmpdu(item, txParams, ppduDuration);
1878 psdu = (mpduList.size() > 1 ? Create<WifiPsdu>(std::move(mpduList))
1879 : Create<WifiPsdu>(item, true));
1880 break;
1881 }
1882 }
1883 }
1884
1885 if (psdu)
1886 {
1887 psdu->SetDuration(hdr.GetDuration() - m_phy->GetSifs() - ppduDuration);
1888 SendPsduMapWithProtection(WifiPsduMap{{staId, psdu}}, txParams);
1889 }
1890 else
1891 {
1892 // send QoS Null frames
1893 SendQosNullFramesInTbPpdu(trigger, hdr);
1894 }
1895}
1896
1897void
1898HeFrameExchangeManager::SendQosNullFramesInTbPpdu(const CtrlTriggerHeader& trigger,
1899 const WifiMacHeader& hdr)
1900{
1901 NS_LOG_FUNCTION(this << trigger << hdr);
1902 NS_ASSERT(trigger.IsBasic() || trigger.IsBsrp());
1903 NS_ASSERT(m_staMac && m_staMac->IsAssociated());
1904
1905 NS_LOG_DEBUG("Requested to send QoS Null frames");
1906
1907 if (!UlMuCsMediumIdle(trigger))
1908 {
1909 return;
1910 }
1911
1912 const auto addr1 =
1913 GetWifiRemoteStationManager()->GetMldAddress(hdr.GetAddr2()).value_or(hdr.GetAddr2());
1915 header.SetAddr1(addr1);
1916 header.SetAddr2(m_mac->GetAddress());
1917 header.SetAddr3(hdr.GetAddr2());
1918 header.SetDsTo();
1919 header.SetDsNotFrom();
1920 // TR3: Sequence numbers for transmitted QoS (+)Null frames may be set
1921 // to any value. (Table 10-3 of 802.11-2016)
1922 header.SetSequenceNumber(0);
1923 // Set the EOSP bit so that NotifyTxToEdca will add the Queue Size
1924 header.SetQosEosp();
1925
1926 WifiTxParameters txParams;
1927 txParams.m_txVector = GetHeTbTxVector(trigger, hdr.GetAddr2());
1928 txParams.m_protection = std::unique_ptr<WifiProtection>(new WifiNoProtection);
1929 txParams.m_acknowledgment = std::unique_ptr<WifiAcknowledgment>(new WifiNoAck);
1930
1931 Time ppduDuration = HePhy::ConvertLSigLengthToHeTbPpduDuration(trigger.GetUlLength(),
1932 txParams.m_txVector,
1933 m_phy->GetPhyBand());
1934 header.SetDuration(hdr.GetDuration() - m_phy->GetSifs() - ppduDuration);
1935
1936 std::vector<Ptr<WifiMpdu>> mpduList;
1937
1938 for (uint8_t tid = 0; tid < 8; ++tid)
1939 {
1940 if (!m_mac->GetBaAgreementEstablishedAsOriginator(hdr.GetAddr2(), tid))
1941 {
1942 NS_LOG_DEBUG("Skipping tid=" << +tid << " because no agreement established");
1943 continue;
1944 }
1945
1946 // We could call TryAddMpdu instead of IsWithinSizeAndTimeLimits below in order to
1947 // get the TX parameters updated automatically. However, aggregating the QoS Null
1948 // frames might fail because MPDU aggregation is disabled by default for VO
1949 // and BK. Therefore, we skip the check on max A-MPDU size and only update the
1950 // TX parameters below.
1951 header.SetQosTid(tid);
1952 auto mpdu = Create<WifiMpdu>(Create<Packet>(), header);
1953 mpdu = CreateAliasIfNeeded(mpdu);
1954 txParams.AddMpdu(mpdu);
1955 UpdateTxDuration(header.GetAddr1(), txParams);
1956
1957 if (!IsWithinSizeAndTimeLimits(txParams.GetSize(header.GetAddr1()),
1958 hdr.GetAddr2(),
1959 txParams,
1960 ppduDuration))
1961 {
1962 txParams.UndoAddMpdu();
1963 break;
1964 }
1965
1966 NS_LOG_DEBUG("Aggregating a QoS Null frame with tid=" << +tid);
1967 txParams.m_acknowledgment = GetAckManager()->TryAddMpdu(mpdu, txParams);
1968 mpduList.push_back(mpdu);
1969 }
1970
1971 if (mpduList.empty())
1972 {
1973 NS_LOG_DEBUG("Not enough time to send a QoS Null frame");
1974 return;
1975 }
1976
1977 Ptr<WifiPsdu> psdu = (mpduList.size() > 1 ? Create<WifiPsdu>(std::move(mpduList))
1978 : Create<WifiPsdu>(mpduList.front(), true));
1979 uint16_t staId = m_staMac->GetAssociationId();
1980 SendPsduMapWithProtection(WifiPsduMap{{staId, psdu}}, txParams);
1981}
1982
1983void
1984HeFrameExchangeManager::ReceiveMuBarTrigger(const CtrlTriggerHeader& trigger,
1985 uint8_t tid,
1986 Time durationId,
1987 double snr)
1988{
1989 NS_LOG_FUNCTION(this << trigger << tid << durationId.As(Time::US) << snr);
1990
1991 auto agreement = m_mac->GetBaAgreementEstablishedAsRecipient(m_bssid, tid);
1992
1993 if (!agreement)
1994 {
1995 NS_LOG_DEBUG("There's not a valid agreement for this BlockAckReq");
1996 return;
1997 }
1998
1999 if (!UlMuCsMediumIdle(trigger))
2000 {
2001 return;
2002 }
2003
2004 NS_LOG_DEBUG("Send Block Ack in TB PPDU");
2005 auto txVector = GetHeTbTxVector(trigger, m_bssid);
2006 SendBlockAck(*agreement, durationId, txVector, snr);
2007}
2008
2009bool
2010HeFrameExchangeManager::IsIntraBssPpdu(Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector) const
2011{
2012 NS_LOG_FUNCTION(this << psdu << txVector);
2013
2014 // "If, based on the MAC address information of a frame carried in a received PPDU, the
2015 // received PPDU satisfies both intra-BSS and inter-BSS conditions, then the received PPDU is
2016 // classified as an intra-BSS PPDU." (Sec. 26.2.2 of 802.11ax-2021)
2017 // Hence, check first if the intra-BSS conditions using MAC address information are satisfied:
2018 // 1. "The PPDU carries a frame that has an RA, TA, or BSSID field value that is equal to
2019 // the BSSID of the BSS in which the STA is associated"
2020 const auto ra = psdu->GetAddr1();
2021 const auto ta = psdu->GetAddr2();
2022 const auto bssid = psdu->GetHeader(0).GetAddr3();
2023 const auto empty = Mac48Address();
2024
2025 if (ra == m_bssid || ta == m_bssid || bssid == m_bssid)
2026 {
2027 return true;
2028 }
2029
2030 // 2. "The PPDU carries a Control frame that does not have a TA field and that has an
2031 // RA field value that matches the saved TXOP holder address of the BSS in which
2032 // the STA is associated"
2033 if (psdu->GetHeader(0).IsCtl() && ta == empty && ra == m_txopHolder)
2034 {
2035 return true;
2036 }
2037
2038 // If we get here, the intra-BSS conditions using MAC address information are not satisfied.
2039 // "If the received PPDU satisfies the intra-BSS conditions using the RXVECTOR parameter
2040 // BSS_COLOR and also satisfies the inter-BSS conditions using MAC address information of a
2041 // frame carried in the PPDU, then the classification made using the MAC address information
2042 // takes precedence."
2043 // Hence, if the inter-BSS conditions using MAC address information are satisfied, the frame
2044 // is classified as inter-BSS
2045 // 1. "The PPDU carries a frame that has a BSSID field, the value of which is not the BSSID
2046 // of the BSS in which the STA is associated"
2047 if (bssid != empty && bssid != m_bssid)
2048 {
2049 return false;
2050 }
2051
2052 // 2. The PPDU carries a frame that does not have a BSSID field but has both an RA field and
2053 // TA field, neither value of which is equal to the BSSID of the BSS in which the STA is
2054 // associated
2055 if (bssid == empty && ta != empty && ra != empty && ta != m_bssid && ra != m_bssid)
2056 {
2057 return false;
2058 }
2059
2060 // If we get here, both intra-BSS and inter-bss conditions using MAC address information
2061 // are not satisfied. Hence, the frame is classified as intra-BSS if the intra-BSS conditions
2062 // using the RXVECTOR parameters are satisfied:
2063 // 1. The RXVECTOR parameter BSS_COLOR of the PPDU carrying the frame is the BSS color of the
2064 // BSS of which the STA is a member
2065 // This condition is used if the BSS is not disabled ("If a STA determines that the BSS color
2066 // is disabled (see 26.17.3.3), then the RXVECTOR parameter BSS_COLOR of a PPDU shall not be
2067 // used to classify the PPDU")
2068 const auto bssColor = m_mac->GetHeConfiguration()->m_bssColor;
2069
2070 // the other two conditions using the RXVECTOR parameter PARTIAL_AID are not implemented
2071 return bssColor != 0 && bssColor == txVector.GetBssColor();
2072}
2073
2074void
2075HeFrameExchangeManager::UpdateNav(Ptr<const WifiPsdu> psdu, const WifiTxVector& txVector)
2076{
2077 NS_LOG_FUNCTION(this << psdu << txVector);
2078
2079 if (!psdu->HasNav())
2080 {
2081 return;
2082 }
2083
2084 if (psdu->GetAddr1() == m_self)
2085 {
2086 // When the received frame's RA is equal to the STA's own MAC address, the STA
2087 // shall not update its NAV (IEEE 802.11-2020, sec. 10.3.2.4)
2088 return;
2089 }
2090
2091 // The intra-BSS NAV is updated by an intra-BSS PPDU. The basic NAV is updated by an
2092 // inter-BSS PPDU or a PPDU that cannot be classified as intra-BSS or inter-BSS.
2093 // (Section 26.2.4 of 802.11ax-2021)
2094 if (!IsIntraBssPpdu(psdu, txVector))
2095 {
2096 NS_LOG_DEBUG("PPDU not classified as intra-BSS, update the basic NAV");
2097 VhtFrameExchangeManager::UpdateNav(psdu, txVector);
2098 return;
2099 }
2100
2101 NS_LOG_DEBUG("PPDU classified as intra-BSS, update the intra-BSS NAV");
2102 Time duration = psdu->GetDuration();
2103 NS_LOG_DEBUG("Duration/ID=" << duration);
2104
2105 if (psdu->GetHeader(0).IsCfEnd())
2106 {
2107 // An HE STA that maintains two NAVs (see 26.2.4) and receives a CF-End frame should reset
2108 // the basic NAV if the received CF-End frame is carried in an inter-BSS PPDU and reset the
2109 // intra-BSS NAV if the received CF-End frame is carried in an intra-BSS PPDU. (Sec. 26.2.5
2110 // of 802.11ax-2021)
2111 NS_LOG_DEBUG("Received CF-End, resetting the intra-BSS NAV");
2112 IntraBssNavResetTimeout();
2113 return;
2114 }
2115
2116 // For all other received frames the STA shall update its NAV when the received
2117 // Duration is greater than the STA's current NAV value (IEEE 802.11-2020 sec. 10.3.2.4)
2118 auto intraBssNavEnd = Simulator::Now() + duration;
2119 if (intraBssNavEnd > m_intraBssNavEnd)
2120 {
2121 m_intraBssNavEnd = intraBssNavEnd;
2122 NS_LOG_DEBUG("Updated intra-BSS NAV=" << m_intraBssNavEnd);
2123
2124 // A STA that used information from an RTS frame as the most recent basis to update
2125 // its NAV setting is permitted to reset its NAV if no PHY-RXSTART.indication
2126 // primitive is received from the PHY during a NAVTimeout period starting when the
2127 // MAC receives a PHY-RXEND.indication primitive corresponding to the detection of
2128 // the RTS frame. NAVTimeout period is equal to:
2129 // (2 x aSIFSTime) + (CTS_Time) + aRxPHYStartDelay + (2 x aSlotTime)
2130 // The “CTS_Time” shall be calculated using the length of the CTS frame and the data
2131 // rate at which the RTS frame used for the most recent NAV update was received
2132 // (IEEE 802.11-2016 sec. 10.3.2.4)
2133 if (psdu->GetHeader(0).IsRts())
2134 {
2135 WifiTxVector ctsTxVector =
2136 GetWifiRemoteStationManager()->GetCtsTxVector(psdu->GetAddr2(), txVector.GetMode());
2137 auto navResetDelay =
2138 2 * m_phy->GetSifs() +
2139 WifiPhy::CalculateTxDuration(GetCtsSize(), ctsTxVector, m_phy->GetPhyBand()) +
2140 m_phy->CalculatePhyPreambleAndHeaderDuration(ctsTxVector) + 2 * m_phy->GetSlot();
2141 m_intraBssNavResetEvent =
2142 Simulator::Schedule(navResetDelay,
2143 &HeFrameExchangeManager::IntraBssNavResetTimeout,
2144 this);
2145 }
2146 }
2147 NS_LOG_DEBUG("Current intra-BSS NAV=" << m_intraBssNavEnd);
2148
2149 m_channelAccessManager->NotifyNavStartNow(duration);
2150}
2151
2152void
2153HeFrameExchangeManager::ClearTxopHolderIfNeeded()
2154{
2155 NS_LOG_FUNCTION(this);
2156 if (m_intraBssNavEnd <= Simulator::Now())
2157 {
2158 m_txopHolder.reset();
2159 }
2160}
2161
2162void
2163HeFrameExchangeManager::NavResetTimeout()
2164{
2165 NS_LOG_FUNCTION(this);
2166 m_navEnd = Simulator::Now();
2167 // Do not reset the TXOP holder because the basic NAV is updated by inter-BSS frames
2168 // The NAV seen by the ChannelAccessManager is now the intra-BSS NAV only
2169 Time intraBssNav = Simulator::GetDelayLeft(m_intraBssNavResetEvent);
2170 m_channelAccessManager->NotifyNavResetNow(intraBssNav);
2171}
2172
2173void
2174HeFrameExchangeManager::IntraBssNavResetTimeout()
2175{
2176 NS_LOG_FUNCTION(this);
2177 m_intraBssNavEnd = Simulator::Now();
2178 ClearTxopHolderIfNeeded();
2179 // The NAV seen by the ChannelAccessManager is now the basic NAV only
2180 Time basicNav = Simulator::GetDelayLeft(m_navResetEvent);
2181 m_channelAccessManager->NotifyNavResetNow(basicNav);
2182}
2183
2184std::optional<Mac48Address>
2185HeFrameExchangeManager::FindTxopHolder(const WifiMacHeader& hdr, const WifiTxVector& txVector)
2186{
2187 NS_LOG_FUNCTION(this << hdr << txVector);
2188
2189 if (hdr.IsTrigger() && hdr.GetAddr2() == m_bssid)
2190 {
2191 return m_bssid;
2192 }
2193 if (!txVector.IsUlMu()) // the sender of a TB PPDU is not the TXOP holder
2194 {
2195 return VhtFrameExchangeManager::FindTxopHolder(hdr, txVector);
2196 }
2197 return std::nullopt;
2198}
2199
2200bool
2201HeFrameExchangeManager::VirtualCsMediumIdle() const
2202{
2203 // For an HE STA maintaining two NAVs, if both the NAV timers are 0, the virtual CS indication
2204 // is that the medium is idle; if at least one of the two NAV timers is nonzero, the virtual CS
2205 // indication is that the medium is busy. (Sec. 26.2.4 of 802.11ax-2021)
2206 return m_navEnd <= Simulator::Now() && m_intraBssNavEnd <= Simulator::Now();
2207}
2208
2209bool
2210HeFrameExchangeManager::UlMuCsMediumIdle(const CtrlTriggerHeader& trigger) const
2211{
2212 if (!trigger.GetCsRequired())
2213 {
2214 NS_LOG_DEBUG("CS not required");
2215 return true;
2216 }
2217
2218 // A non-AP STA does not consider the intra-BSS NAV in determining whether to respond to a
2219 // Trigger frame sent by the AP with which the non-AP STA is associated.
2220 // A non-AP STA considers the basic NAV in determining whether to respond to a Trigger frame
2221 // sent by the AP with which the non-AP STA is associated. (Sec. 26.5.2.5 of 802.11ax-2021)
2222 const Time now = Simulator::Now();
2223 if (m_navEnd > now)
2224 {
2225 NS_LOG_DEBUG("Basic NAV indicates medium busy");
2226 return false;
2227 }
2228
2229 NS_ASSERT_MSG(m_staMac, "UL MU CS is only performed by non-AP STAs");
2230 const auto userInfoIt = trigger.FindUserInfoWithAid(m_staMac->GetAssociationId());
2231 NS_ASSERT_MSG(userInfoIt != trigger.end(),
2232 "No User Info field for STA (" << m_self
2233 << ") AID=" << m_staMac->GetAssociationId());
2234
2235 std::set<uint8_t> indices;
2236
2237 if (trigger.IsMuRts())
2238 {
2239 auto ctsTxVector = GetCtsTxVectorAfterMuRts(trigger, m_staMac->GetAssociationId());
2240 auto bw = ctsTxVector.GetChannelWidth();
2241 indices = m_phy->GetOperatingChannel().GetAll20MHzChannelIndicesInPrimary(bw);
2242 }
2243 else
2244 {
2245 indices =
2246 m_phy->GetOperatingChannel().Get20MHzIndicesCoveringRu(userInfoIt->GetRuAllocation(),
2247 trigger.GetUlBandwidth());
2248 }
2249 return !m_channelAccessManager->GetPer20MHzBusy(indices);
2250}
2251
2252void
2253HeFrameExchangeManager::ReceiveMpdu(Ptr<const WifiMpdu> mpdu,
2254 RxSignalInfo rxSignalInfo,
2255 const WifiTxVector& txVector,
2256 bool inAmpdu)
2257{
2258 // The received MPDU is either broadcast or addressed to this station
2259 NS_ASSERT(mpdu->GetHeader().GetAddr1().IsGroup() || mpdu->GetHeader().GetAddr1() == m_self);
2260
2261 const WifiMacHeader& hdr = mpdu->GetHeader();
2262
2263 if (txVector.IsUlMu() && m_txTimer.IsRunning() &&
2264 m_txTimer.GetReason() == WifiTxTimer::WAIT_TB_PPDU_AFTER_BASIC_TF)
2265 {
2266 Mac48Address sender = hdr.GetAddr2();
2267 NS_ASSERT(m_txParams.m_acknowledgment &&
2268 m_txParams.m_acknowledgment->method == WifiAcknowledgment::UL_MU_MULTI_STA_BA);
2269 auto acknowledgment = static_cast<WifiUlMuMultiStaBa*>(m_txParams.m_acknowledgment.get());
2270 std::size_t index = acknowledgment->baType.m_bitmapLen.size();
2271
2272 if (!m_txTimer.GetStasExpectedToRespond().contains(sender))
2273 {
2274 NS_LOG_WARN("Received a TB PPDU from an unexpected station: " << sender);
2275 return;
2276 }
2277
2278 if (hdr.IsBlockAckReq())
2279 {
2280 NS_LOG_DEBUG("Received a BlockAckReq in a TB PPDU from " << sender);
2281
2282 CtrlBAckRequestHeader blockAckReq;
2283 mpdu->GetPacket()->PeekHeader(blockAckReq);
2284 NS_ABORT_MSG_IF(blockAckReq.IsMultiTid(), "Multi-TID BlockAckReq not supported");
2285 uint8_t tid = blockAckReq.GetTidInfo();
2286 GetBaManager(tid)->NotifyGotBlockAckRequest(
2287 m_mac->GetMldAddress(sender).value_or(sender),
2288 tid,
2289 blockAckReq.GetStartingSequence());
2290
2291 // Block Acknowledgment context
2292 acknowledgment->stationsReceivingMultiStaBa.emplace(std::make_pair(sender, tid), index);
2293 acknowledgment->baType.m_bitmapLen.push_back(
2294 m_mac->GetBaTypeAsRecipient(sender, tid).m_bitmapLen.at(0));
2295 uint16_t staId = txVector.GetHeMuUserInfoMap().begin()->first;
2296 m_muSnrTag.Set(staId, rxSignalInfo.snr);
2297 }
2298 else if (hdr.IsQosData() && !inAmpdu && hdr.GetQosAckPolicy() == WifiMacHeader::NORMAL_ACK)
2299 {
2300 NS_LOG_DEBUG("Received an S-MPDU in a TB PPDU from " << sender << " (" << *mpdu << ")");
2301
2302 uint8_t tid = hdr.GetQosTid();
2303 GetBaManager(tid)->NotifyGotMpdu(mpdu);
2304
2305 // Acknowledgment context of Multi-STA Block Acks
2306 acknowledgment->stationsReceivingMultiStaBa.emplace(std::make_pair(sender, tid), index);
2307 acknowledgment->baType.m_bitmapLen.push_back(0);
2308 uint16_t staId = txVector.GetHeMuUserInfoMap().begin()->first;
2309 m_muSnrTag.Set(staId, rxSignalInfo.snr);
2310 }
2311 else if (!(hdr.IsQosData() && !hdr.HasData() && !inAmpdu))
2312 {
2313 // The other case handled by this function is when we receive a QoS Null frame
2314 // that is not in an A-MPDU. For all other cases, the reception is handled by
2315 // parent classes. In particular, in case of a QoS data frame in A-MPDU, we
2316 // have to wait until the A-MPDU reception is completed, but we let the
2317 // parent classes notify the Block Ack agreement of the reception of this MPDU
2318 VhtFrameExchangeManager::ReceiveMpdu(mpdu, rxSignalInfo, txVector, inAmpdu);
2319 return;
2320 }
2321
2322 // Schedule the transmission of a Multi-STA BlockAck frame if needed
2323 if (!acknowledgment->stationsReceivingMultiStaBa.empty() && !m_multiStaBaEvent.IsPending())
2324 {
2325 m_multiStaBaEvent = Simulator::Schedule(m_phy->GetSifs(),
2326 &HeFrameExchangeManager::SendMultiStaBlockAck,
2327 this,
2328 std::cref(m_txParams),
2329 mpdu->GetHeader().GetDuration());
2330 }
2331
2332 // remove the sender from the set of stations that are expected to send a TB PPDU
2333 m_txTimer.GotResponseFrom(sender);
2334
2335 if (m_txTimer.GetStasExpectedToRespond().empty())
2336 {
2337 // we do not expect any other BlockAck frame
2338 m_txTimer.Cancel();
2339 m_channelAccessManager->NotifyAckTimeoutResetNow();
2340
2341 if (!m_multiStaBaEvent.IsPending())
2342 {
2343 // all of the stations that replied with a TB PPDU sent QoS Null frames.
2344 NS_LOG_DEBUG("Continue the TXOP");
2345 m_psduMap.clear();
2346 m_edca->ResetCw(m_linkId);
2347 TransmissionSucceeded();
2348 }
2349 }
2350
2351 // the received TB PPDU has been processed
2352 return;
2353 }
2354
2355 if (txVector.IsUlMu() && m_txTimer.IsRunning() &&
2356 m_txTimer.GetReason() == WifiTxTimer::WAIT_QOS_NULL_AFTER_BSRP_TF &&
2357 !inAmpdu) // if in A-MPDU, processing is done at the end of A-MPDU reception
2358 {
2359 const auto& sender = hdr.GetAddr2();
2360
2361 if (!m_txTimer.GetStasExpectedToRespond().contains(sender))
2362 {
2363 NS_LOG_WARN("Received a TB PPDU from an unexpected station: " << sender);
2364 return;
2365 }
2366 if (!(hdr.IsQosData() && !hdr.HasData()))
2367 {
2368 NS_LOG_WARN("No QoS Null frame in the received MPDU");
2369 return;
2370 }
2371
2372 NS_LOG_DEBUG("Received a QoS Null frame in a TB PPDU from " << sender);
2373 ReceivedQosNullAfterBsrpTf(sender);
2374
2375 // the received TB PPDU has been processed
2376 return;
2377 }
2378
2379 if (hdr.IsCtl())
2380 {
2381 if (hdr.IsCts() && m_txTimer.IsRunning() &&
2382 m_txTimer.GetReason() == WifiTxTimer::WAIT_CTS && m_psduMap.size() == 1)
2383 {
2384 NS_ABORT_MSG_IF(inAmpdu, "Received CTS as part of an A-MPDU");
2385 NS_ASSERT(hdr.GetAddr1() == m_self);
2386
2387 Mac48Address sender = m_psduMap.begin()->second->GetAddr1();
2388 NS_LOG_DEBUG("Received CTS from=" << sender);
2389
2390 SnrTag tag;
2391 mpdu->GetPacket()->PeekPacketTag(tag);
2392 GetWifiRemoteStationManager()->ReportRxOk(sender, rxSignalInfo, txVector);
2393 GetWifiRemoteStationManager()->ReportRtsOk(m_psduMap.begin()->second->GetHeader(0),
2394 rxSignalInfo.snr,
2395 txVector.GetMode(),
2396 tag.Get());
2397
2398 m_txTimer.Cancel();
2399 m_channelAccessManager->NotifyCtsTimeoutResetNow();
2400 ProtectionCompleted();
2401 }
2402 else if (hdr.IsCts() && m_txTimer.IsRunning() &&
2403 m_txTimer.GetReason() == WifiTxTimer::WAIT_CTS_AFTER_MU_RTS)
2404 {
2405 NS_ABORT_MSG_IF(inAmpdu, "Received CTS as part of an A-MPDU");
2406 NS_ASSERT(hdr.GetAddr1() == m_self);
2407
2408 NS_LOG_DEBUG("Received a CTS frame in response to an MU-RTS");
2409
2410 m_txTimer.Cancel();
2411 m_channelAccessManager->NotifyCtsTimeoutResetNow();
2412 ProtectionCompleted();
2413 }
2414 else if (hdr.IsAck() && m_txTimer.IsRunning() &&
2415 m_txTimer.GetReason() == WifiTxTimer::WAIT_NORMAL_ACK_AFTER_DL_MU_PPDU)
2416 {
2417 NS_ASSERT(hdr.GetAddr1() == m_self);
2418 NS_ASSERT(m_txParams.m_acknowledgment);
2419 NS_ASSERT(m_txParams.m_acknowledgment->method ==
2420 WifiAcknowledgment::DL_MU_BAR_BA_SEQUENCE);
2421
2422 auto acknowledgment =
2423 static_cast<WifiDlMuBarBaSequence*>(m_txParams.m_acknowledgment.get());
2424 NS_ASSERT(acknowledgment->stationsReplyingWithNormalAck.size() == 1);
2425 NS_ASSERT(m_apMac);
2426 uint16_t staId = m_apMac->GetAssociationId(
2427 acknowledgment->stationsReplyingWithNormalAck.begin()->first,
2428 m_linkId);
2429 auto it = m_psduMap.find(staId);
2430 NS_ASSERT(it != m_psduMap.end());
2431 NS_ASSERT(it->second->GetAddr1() ==
2432 acknowledgment->stationsReplyingWithNormalAck.begin()->first);
2433 SnrTag tag;
2434 mpdu->GetPacket()->PeekPacketTag(tag);
2435 ReceivedNormalAck(*it->second->begin(),
2436 m_txParams.m_txVector,
2437 txVector,
2438 rxSignalInfo,
2439 tag.Get());
2440 m_psduMap.clear();
2441 }
2442 // TODO the PHY should not pass us a non-TB PPDU if we are waiting for a
2443 // TB PPDU. However, processing the PHY header is done by the PHY entity
2444 // corresponding to the modulation class of the PPDU being received, hence
2445 // it is not possible to check if a valid TRIGVECTOR is stored when receiving
2446 // PPDUs of older modulation classes. Therefore, we check here that we are
2447 // actually receiving a TB PPDU.
2448 else if (hdr.IsBlockAck() && txVector.IsUlMu() && m_txTimer.IsRunning() &&
2449 m_txTimer.GetReason() == WifiTxTimer::WAIT_BLOCK_ACKS_IN_TB_PPDU)
2450 {
2451 Mac48Address sender = hdr.GetAddr2();
2452 NS_LOG_DEBUG("Received BlockAck in TB PPDU from=" << sender);
2453
2454 SnrTag tag;
2455 mpdu->GetPacket()->PeekPacketTag(tag);
2456
2457 // notify the Block Ack Manager
2458 CtrlBAckResponseHeader blockAck;
2459 mpdu->GetPacket()->PeekHeader(blockAck);
2460 uint8_t tid = blockAck.GetTidInfo();
2461 std::pair<uint16_t, uint16_t> ret =
2462 GetBaManager(tid)->NotifyGotBlockAck(m_linkId,
2463 blockAck,
2464 m_mac->GetMldAddress(sender).value_or(sender),
2465 {tid});
2466 GetWifiRemoteStationManager()->ReportAmpduTxStatus(sender,
2467 ret.first,
2468 ret.second,
2469 rxSignalInfo.snr,
2470 tag.Get(),
2471 m_txParams.m_txVector);
2472
2473 // remove the sender from the set of stations that are expected to send a BlockAck
2474 if (!m_txTimer.GetStasExpectedToRespond().contains(sender))
2475 {
2476 NS_LOG_WARN("Received a BlockAck from an unexpected stations: " << sender);
2477 return;
2478 }
2479
2480 m_txTimer.GotResponseFrom(sender);
2481
2482 if (m_txTimer.GetStasExpectedToRespond().empty())
2483 {
2484 // we do not expect any other BlockAck frame
2485 m_txTimer.Cancel();
2486 m_channelAccessManager->NotifyAckTimeoutResetNow();
2487 if (m_triggerFrame)
2488 {
2489 // this is strictly needed for DL_MU_TF_MU_BAR only
2490 m_triggerFrame = nullptr;
2491 }
2492
2493 m_edca->ResetCw(m_linkId);
2494 m_psduMap.clear();
2495 TransmissionSucceeded();
2496 }
2497 }
2498 else if (hdr.IsBlockAck() && m_txTimer.IsRunning() &&
2499 m_txTimer.GetReason() == WifiTxTimer::WAIT_BLOCK_ACK_AFTER_TB_PPDU)
2500 {
2501 CtrlBAckResponseHeader blockAck;
2502 mpdu->GetPacket()->PeekHeader(blockAck);
2503
2504 NS_ABORT_MSG_IF(!blockAck.IsMultiSta(),
2505 "A Multi-STA BlockAck is expected after a TB PPDU");
2506 NS_LOG_DEBUG("Received a Multi-STA BlockAck from=" << hdr.GetAddr2());
2507 m_txTimer.GotResponseFrom(hdr.GetAddr2());
2508
2509 NS_ASSERT(m_staMac && m_staMac->IsAssociated());
2510 if (hdr.GetAddr2() != m_bssid)
2511 {
2512 NS_LOG_DEBUG("The sender is not the AP we are associated with");
2513 return;
2514 }
2515
2516 uint16_t staId = m_staMac->GetAssociationId();
2517 std::vector<uint32_t> indices = blockAck.FindPerAidTidInfoWithAid(staId);
2518
2519 if (indices.empty())
2520 {
2521 NS_LOG_DEBUG("No Per AID TID Info subfield intended for me");
2522 return;
2523 }
2524
2525 MuSnrTag tag;
2526 mpdu->GetPacket()->PeekPacketTag(tag);
2527
2528 // notify the Block Ack Manager
2529 for (const auto& index : indices)
2530 {
2531 uint8_t tid = blockAck.GetTidInfo(index);
2532
2533 if (blockAck.GetAckType(index) && tid < 8)
2534 {
2535 // Acknowledgment context
2536 NS_ABORT_IF(m_psduMap.empty() || m_psduMap.begin()->first != staId);
2537 GetBaManager(tid)->NotifyGotAck(m_linkId, *m_psduMap.at(staId)->begin());
2538 }
2539 else
2540 {
2541 // Block Acknowledgment or All-ack context
2542 if (blockAck.GetAckType(index) && tid == 14)
2543 {
2544 // All-ack context, we need to determine the actual TID(s) of the PSDU
2545 NS_ASSERT(indices.size() == 1);
2546 NS_ABORT_IF(m_psduMap.empty() || m_psduMap.begin()->first != staId);
2547 std::set<uint8_t> tids = m_psduMap.at(staId)->GetTids();
2548 NS_ABORT_MSG_IF(tids.size() > 1, "Multi-TID A-MPDUs not supported yet");
2549 tid = *tids.begin();
2550 }
2551
2552 std::pair<uint16_t, uint16_t> ret = GetBaManager(tid)->NotifyGotBlockAck(
2553 m_linkId,
2554 blockAck,
2555 m_mac->GetMldAddress(hdr.GetAddr2()).value_or(hdr.GetAddr2()),
2556 {tid},
2557 index);
2558 GetWifiRemoteStationManager()->ReportAmpduTxStatus(hdr.GetAddr2(),
2559 ret.first,
2560 ret.second,
2561 rxSignalInfo.snr,
2562 tag.Get(staId),
2563 m_txParams.m_txVector);
2564 }
2565
2566 if (m_psduMap.at(staId)->GetHeader(0).IsQosData() &&
2567 (blockAck.GetAckType(index) // Ack or All-ack context
2568 || std::any_of(blockAck.GetBitmap(index).begin(),
2569 blockAck.GetBitmap(index).end(),
2570 [](uint8_t b) { return b != 0; })))
2571 {
2572 NS_ASSERT(m_psduMap.at(staId)->GetHeader(0).HasData());
2573 NS_ASSERT(m_psduMap.at(staId)->GetHeader(0).GetQosTid() == tid);
2574 // the station has received a response from the AP for the HE TB PPDU
2575 // transmitted in response to a Basic Trigger Frame and at least one
2576 // MPDU was acknowledged. Therefore, it needs to update the access
2577 // parameters if it received an MU EDCA Parameter Set element.
2578 m_mac->GetQosTxop(tid)->StartMuEdcaTimerNow(m_linkId);
2579 }
2580 }
2581
2582 // cancel the timer
2583 m_txTimer.Cancel();
2584 m_channelAccessManager->NotifyAckTimeoutResetNow();
2585 // dequeue BlockAckReq frames included in acknowledged TB PPDUs (if any)
2586 for (const auto& [staId, psdu] : m_psduMap)
2587 {
2588 if (psdu->GetNMpdus() == 1 && psdu->GetHeader(0).IsBlockAckReq())
2589 {
2590 DequeuePsdu(psdu);
2591 }
2592 }
2593 m_psduMap.clear();
2594 }
2595 else if (hdr.IsBlockAck() && m_txTimer.IsRunning() &&
2596 m_txTimer.GetReason() == WifiTxTimer::WAIT_BLOCK_ACK)
2597 {
2598 // this BlockAck frame may have been sent in response to a DL MU PPDU with
2599 // acknowledgment in SU format or one of the consequent BlockAckReq frames.
2600 // We clear the PSDU map and let parent classes continue processing this frame.
2601 m_psduMap.clear();
2602 VhtFrameExchangeManager::ReceiveMpdu(mpdu, rxSignalInfo, txVector, inAmpdu);
2603 }
2604 else if (hdr.IsTrigger())
2605 {
2606 // Trigger Frames are only processed by STAs
2607 if (!m_staMac)
2608 {
2609 return;
2610 }
2611
2612 // A Trigger Frame in an A-MPDU is processed when the A-MPDU is fully received
2613 if (inAmpdu)
2614 {
2615 m_triggerFrameInAmpdu = true;
2616 return;
2617 }
2618
2619 CtrlTriggerHeader trigger;
2620 mpdu->GetPacket()->PeekHeader(trigger);
2621
2622 if (hdr.GetAddr1() != m_self &&
2623 (!hdr.GetAddr1().IsBroadcast() || !m_staMac->IsAssociated() ||
2624 hdr.GetAddr2() != m_bssid // not sent by the AP this STA is associated with
2625 || trigger.FindUserInfoWithAid(m_staMac->GetAssociationId()) == trigger.end()))
2626 {
2627 // not addressed to us
2628 return;
2629 }
2630
2631 uint16_t staId = m_staMac->GetAssociationId();
2632
2633 if (trigger.IsMuRts())
2634 {
2635 Mac48Address sender = hdr.GetAddr2();
2636 NS_LOG_DEBUG("Received MU-RTS Trigger Frame from=" << sender);
2637 GetWifiRemoteStationManager()->ReportRxOk(sender, rxSignalInfo, txVector);
2638
2639 // If a non-AP STA receives an MU-RTS Trigger frame, the non-AP STA shall commence
2640 // the transmission of a CTS frame response at the SIFS time boundary after
2641 // the end of a received PPDU when all the following conditions are met:
2642 // - The MU-RTS Trigger frame has one of the User Info fields addressed to
2643 // the non-AP STA (this is guaranteed if we get here)
2644 // - The UL MU CS condition indicates that the medium is idle
2645 // (Sec. 26.2.6.3 of 802.11ax-2021)
2646 NS_LOG_DEBUG("Schedule CTS");
2647 Simulator::Schedule(m_phy->GetSifs(),
2648 &HeFrameExchangeManager::SendCtsAfterMuRts,
2649 this,
2650 hdr,
2651 trigger,
2652 rxSignalInfo.snr);
2653 }
2654 else if (trigger.IsMuBar())
2655 {
2656 Mac48Address sender = hdr.GetAddr2();
2657 NS_LOG_DEBUG("Received MU-BAR Trigger Frame from=" << sender);
2658 GetWifiRemoteStationManager()->ReportRxOk(sender, rxSignalInfo, txVector);
2659
2660 auto userInfoIt = trigger.FindUserInfoWithAid(staId);
2661 NS_ASSERT(userInfoIt != trigger.end());
2662 CtrlBAckRequestHeader blockAckReq = userInfoIt->GetMuBarTriggerDepUserInfo();
2663 NS_ABORT_MSG_IF(blockAckReq.IsMultiTid(), "Multi-TID BlockAckReq not supported");
2664 uint8_t tid = blockAckReq.GetTidInfo();
2665
2666 GetBaManager(tid)->NotifyGotBlockAckRequest(
2667 m_mac->GetMldAddress(sender).value_or(sender),
2668 tid,
2669 blockAckReq.GetStartingSequence());
2670
2671 Simulator::Schedule(m_phy->GetSifs(),
2672 &HeFrameExchangeManager::ReceiveMuBarTrigger,
2673 this,
2674 trigger,
2675 tid,
2676 hdr.GetDuration(),
2677 rxSignalInfo.snr);
2678 }
2679 else if (trigger.IsBasic())
2680 {
2681 Simulator::Schedule(m_phy->GetSifs(),
2682 &HeFrameExchangeManager::ReceiveBasicTrigger,
2683 this,
2684 trigger,
2685 hdr);
2686 }
2687 else if (trigger.IsBsrp())
2688 {
2689 Simulator::Schedule(m_phy->GetSifs(),
2690 &HeFrameExchangeManager::SendQosNullFramesInTbPpdu,
2691 this,
2692 trigger,
2693 hdr);
2694 }
2695 }
2696 else
2697 {
2698 // the received control frame cannot be handled here
2699 VhtFrameExchangeManager::ReceiveMpdu(mpdu, rxSignalInfo, txVector, inAmpdu);
2700 }
2701
2702 // the received control frame has been processed
2703 return;
2704 }
2705
2706 // the received frame cannot be handled here
2707 VhtFrameExchangeManager::ReceiveMpdu(mpdu, rxSignalInfo, txVector, inAmpdu);
2708 ;
2709}
2710
2711void
2712HeFrameExchangeManager::EndReceiveAmpdu(Ptr<const WifiPsdu> psdu,
2713 const RxSignalInfo& rxSignalInfo,
2714 const WifiTxVector& txVector,
2715 const std::vector<bool>& perMpduStatus)
2716{
2717 std::set<uint8_t> tids = psdu->GetTids();
2718
2719 if (txVector.IsUlMu() && m_txTimer.IsRunning() &&
2720 m_txTimer.GetReason() == WifiTxTimer::WAIT_TB_PPDU_AFTER_BASIC_TF)
2721 {
2722 Mac48Address sender = psdu->GetAddr2();
2723 NS_ASSERT(m_txParams.m_acknowledgment &&
2724 m_txParams.m_acknowledgment->method == WifiAcknowledgment::UL_MU_MULTI_STA_BA);
2725 auto acknowledgment = static_cast<WifiUlMuMultiStaBa*>(m_txParams.m_acknowledgment.get());
2726 std::size_t index = acknowledgment->baType.m_bitmapLen.size();
2727
2728 if (!m_txTimer.GetStasExpectedToRespond().contains(sender))
2729 {
2730 NS_LOG_WARN("Received a TB PPDU from an unexpected station: " << sender);
2731 return;
2732 }
2733
2734 NS_LOG_DEBUG("Received an A-MPDU in a TB PPDU from " << sender << " (" << *psdu << ")");
2735
2736 if (std::any_of(tids.begin(), tids.end(), [&psdu](uint8_t tid) {
2737 return psdu->GetAckPolicyForTid(tid) == WifiMacHeader::NORMAL_ACK;
2738 }))
2739 {
2740 if (std::all_of(perMpduStatus.cbegin(), perMpduStatus.cend(), [](bool v) { return v; }))
2741 {
2742 // All-ack context
2743 acknowledgment->stationsReceivingMultiStaBa.emplace(std::make_pair(sender, 14),
2744 index);
2745 acknowledgment->baType.m_bitmapLen.push_back(0);
2746 }
2747 else
2748 {
2749 // Block Acknowledgment context
2750 std::size_t i = 0;
2751 for (const auto& tid : tids)
2752 {
2753 acknowledgment->stationsReceivingMultiStaBa.emplace(std::make_pair(sender, tid),
2754 index + i++);
2755 acknowledgment->baType.m_bitmapLen.push_back(
2756 m_mac->GetBaTypeAsRecipient(sender, tid).m_bitmapLen.at(0));
2757 }
2758 }
2759 uint16_t staId = txVector.GetHeMuUserInfoMap().begin()->first;
2760 m_muSnrTag.Set(staId, rxSignalInfo.snr);
2761 }
2762
2763 // Schedule the transmission of a Multi-STA BlockAck frame if needed
2764 if (!acknowledgment->stationsReceivingMultiStaBa.empty() && !m_multiStaBaEvent.IsPending())
2765 {
2766 m_multiStaBaEvent = Simulator::Schedule(m_phy->GetSifs(),
2767 &HeFrameExchangeManager::SendMultiStaBlockAck,
2768 this,
2769 std::cref(m_txParams),
2770 psdu->GetDuration());
2771 }
2772
2773 // remove the sender from the set of stations that are expected to send a TB PPDU
2774 m_txTimer.GotResponseFrom(sender);
2775
2776 if (m_txTimer.GetStasExpectedToRespond().empty())
2777 {
2778 // we do not expect any other BlockAck frame
2779 m_txTimer.Cancel();
2780 m_channelAccessManager->NotifyAckTimeoutResetNow();
2781
2782 if (!m_multiStaBaEvent.IsPending())
2783 {
2784 // all of the stations that replied with a TB PPDU sent QoS Null frames.
2785 NS_LOG_DEBUG("Continue the TXOP");
2786 m_psduMap.clear();
2787 m_edca->ResetCw(m_linkId);
2788 TransmissionSucceeded();
2789 }
2790 }
2791
2792 // the received TB PPDU has been processed
2793 return;
2794 }
2795
2796 if (txVector.IsUlMu() && m_txTimer.IsRunning() &&
2797 m_txTimer.GetReason() == WifiTxTimer::WAIT_QOS_NULL_AFTER_BSRP_TF)
2798 {
2799 Mac48Address sender = psdu->GetAddr2();
2800
2801 if (!m_txTimer.GetStasExpectedToRespond().contains(sender))
2802 {
2803 NS_LOG_WARN("Received a TB PPDU from an unexpected station: " << sender);
2804 return;
2805 }
2806 if (std::none_of(psdu->begin(), psdu->end(), [](Ptr<WifiMpdu> mpdu) {
2807 return mpdu->GetHeader().IsQosData() && !mpdu->GetHeader().HasData();
2808 }))
2809 {
2810 NS_LOG_WARN("No QoS Null frame in the received PSDU");
2811 return;
2812 }
2813
2814 NS_LOG_DEBUG("Received QoS Null frames in a TB PPDU from " << sender);
2815 ReceivedQosNullAfterBsrpTf(sender);
2816
2817 // the received TB PPDU has been processed
2818 return;
2819 }
2820
2821 if (m_triggerFrameInAmpdu)
2822 {
2823 // the received A-MPDU contains a Trigger Frame. It is now time to handle it.
2824 auto psduIt = psdu->begin();
2825 while (psduIt != psdu->end())
2826 {
2827 if ((*psduIt)->GetHeader().IsTrigger())
2828 {
2829 ReceiveMpdu(*psduIt, rxSignalInfo, txVector, false);
2830 }
2831 psduIt++;
2832 }
2833
2834 m_triggerFrameInAmpdu = false;
2835 return;
2836 }
2837
2838 // the received frame cannot be handled here
2839 VhtFrameExchangeManager::EndReceiveAmpdu(psdu, rxSignalInfo, txVector, perMpduStatus);
2840}
2841
2842void
2843HeFrameExchangeManager::ReceivedQosNullAfterBsrpTf(Mac48Address sender)
2844{
2845 NS_LOG_FUNCTION(this << sender);
2846
2847 NS_ASSERT(m_txTimer.IsRunning() &&
2848 m_txTimer.GetReason() == WifiTxTimer::WAIT_QOS_NULL_AFTER_BSRP_TF);
2849
2850 // remove the sender from the set of stations that are expected to send a TB PPDU
2851 m_txTimer.GotResponseFrom(sender);
2852
2853 if (m_txTimer.GetStasExpectedToRespond().empty())
2854 {
2855 // we do not expect any other response
2856 m_channelAccessManager->NotifyAckTimeoutResetNow();
2857
2858 NS_ASSERT(m_edca);
2859 m_psduMap.clear();
2860 m_edca->ResetCw(m_linkId);
2861 TransmissionSucceeded();
2862 // we reset the TX timer after calling TransmissionSucceeded, so that the latter can
2863 // check whether the reason for the last timer is WAIT_QOS_NULL_AFTER_BSRP_TF
2864 m_txTimer.Cancel();
2865 }
2866}
2867
2868} // namespace ns3
#define Max(a, b)
AttributeValue implementation for Boolean.
Definition boolean.h:26
Headers for BlockAckRequest.
uint16_t GetStartingSequence() const
Return the starting sequence number.
uint8_t GetTidInfo() const
Return the Traffic ID (TID).
bool IsMultiTid() const
Check if the current Ack Policy has Multi-TID Block Ack.
Headers for BlockAck response.
std::vector< uint32_t > FindPerAidTidInfoWithAid(uint16_t aid) const
For Multi-STA Block Acks, get the indices of the Per AID TID Info subfields carrying the given AID in...
uint16_t GetStartingSequence(std::size_t index=0) const
For Block Ack variants other than Multi-STA Block Ack, get the starting sequence number.
uint8_t GetTidInfo(std::size_t index=0) const
For Block Ack variants other than Multi-STA Block Ack, get the TID_INFO subfield of the BA Control fi...
const std::vector< uint8_t > & GetBitmap(std::size_t index=0) const
Return a const reference to the bitmap from the BlockAck response header.
void SetAckType(bool type, std::size_t index)
For Multi-STA Block Acks, set the Ack Type subfield of the Per AID TID Info subfield identified by th...
void SetTidInfo(uint8_t tid, std::size_t index=0)
For Block Ack variants other than Multi-STA Block Ack, set the TID_INFO subfield of the BA Control fi...
void SetType(BlockAckType type)
Set the block ack type.
void SetAid11(uint16_t aid, std::size_t index)
For Multi-STA Block Acks, set the AID11 subfield of the Per AID TID Info subfield identified by the g...
bool GetAckType(std::size_t index) const
For Multi-STA Block Acks, get the Ack Type subfield of the Per AID TID Info subfield identified by th...
bool IsMultiSta() const
Check if the BlockAck frame variant is Multi-STA Block Ack.
Headers for Trigger frames.
bool IsBasic() const
Check if this is a Basic Trigger frame.
void SetApTxPower(int8_t power)
Set the AP TX Power subfield of the Common Info field.
ConstIterator end() const
Get a const iterator indicating past-the-last User Info field in the list.
WifiTxVector GetHeTbTxVector(uint16_t staId) const
Get the TX vector that the station with the given STA-ID will use to send the HE TB PPDU solicited by...
bool IsMuRts() const
Check if this is a MU-RTS Trigger frame.
bool IsBsrp() const
Check if this is a Buffer Status Report Poll Trigger frame.
bool IsMuBar() const
Check if this is a MU-BAR Trigger frame.
std::size_t GetNUserInfoFields() const
Get the number of User Info fields in this Trigger Frame.
ConstIterator FindUserInfoWithAid(ConstIterator start, uint16_t aid12) const
Get a const iterator pointing to the first User Info field found (starting from the one pointed to by...
void SetCsRequired(bool cs)
Set the CS Required subfield of the Common Info field.
uint16_t GetUlLength() const
Get the UL Length subfield of the Common Info field.
MHz_u GetUlBandwidth() const
Get the bandwidth of the solicited HE TB PPDU.
TriggerFrameVariant GetVariant() const
Get the Common Info field variant.
Time GetGuardInterval() const
Get the guard interval duration of the solicited HE TB PPDU.
bool GetCsRequired() const
Get the CS Required subfield of the Common Info field.
int8_t GetApTxPower() const
Get the power value (dBm) indicated by the AP TX Power subfield of the Common Info field.
void Cancel()
This method is syntactic sugar for the ns3::Simulator::Cancel method.
Definition event-id.cc:44
bool IsPending() const
This method is syntactic sugar for !IsExpired().
Definition event-id.cc:65
std::set< Mac48Address > m_sentRtsTo
the STA(s) which we sent an RTS to (waiting for CTS)
void DoCtsTimeout(Ptr< WifiPsdu > psdu)
Take required actions when the CTS timer fired after sending an RTS to protect the given PSDU expires...
uint8_t m_linkId
the ID of the link this object is associated with
Ptr< WifiMac > m_mac
the MAC layer on this station
bool m_protectedIfResponded
whether a STA is assumed to be protected if replied to a frame requiring acknowledgment
virtual void SetWifiMac(const Ptr< WifiMac > mac)
Set the MAC layer to use.
Ptr< WifiRemoteStationManager > GetWifiRemoteStationManager() const
virtual void Reset()
Reset this frame exchange manager.
Mac48Address m_self
the MAC address of this device
virtual void StartProtection(const WifiTxParameters &txParams)
Start the protection mechanism indicated by the given TX parameters.
WifiTxTimer m_txTimer
the timer set upon frame transmission
std::set< Mac48Address > m_protectedStas
STAs that have replied to an RTS in this TXOP.
virtual Time GetRtsDurationId(const WifiTxVector &rtsTxVector, Time txDuration, Time response) const
Compute how to set the Duration/ID field of an RTS frame to send to protect a frame transmitted with ...
virtual void ProtectionCompleted()
Transmit prepared frame immediately, if no protection was used, or in a SIFS, if protection was compl...
virtual void NotifyChannelReleased(Ptr< Txop > txop)
Notify the given Txop that channel has been released.
virtual void CtsTimeout(Ptr< WifiMpdu > rts, const WifiTxVector &txVector)
Called when the CTS timeout expires.
virtual void TransmissionSucceeded()
Take necessary actions upon a transmission success.
Ptr< WifiPhy > m_phy
the PHY layer on this station
Ptr< WifiMpdu > DropMpduIfRetryLimitReached(Ptr< WifiPsdu > psdu)
Wrapper for the GetMpdusToDropOnTxFailure function of the remote station manager that additionally dr...
std::set< Mac48Address > m_sentFrameTo
the STA(s) to which we sent a frame requesting a response
Ptr< ChannelAccessManager > m_channelAccessManager
the channel access manager
MHz_u m_allowedWidth
the allowed width for the current transmission
virtual void RxStartIndication(WifiTxVector txVector, Time psduDuration)
virtual Time GetMuRtsDurationId(uint32_t muRtsSize, const WifiTxVector &muRtsTxVector, Time txDuration, Time response) const
Compute how to set the Duration/ID field of an MU-RTS Trigger Frame to send to protect a frame transm...
Ptr< ApWifiMac > m_apMac
MAC pointer (null if not an AP)
void DoDispose() override
Destructor implementation.
void Reset() override
Reset this frame exchange manager.
virtual void SendMuRts(const WifiTxParameters &txParams)
Send an MU-RTS to begin an MU-RTS/CTS frame exchange protecting an MU PPDU.
WifiTxParameters m_txParams
the TX parameters for the current PPDU
void RxStartIndication(WifiTxVector txVector, Time psduDuration) override
void CalculateAcknowledgmentTime(WifiAcknowledgment *acknowledgment) const override
Calculate the time required to acknowledge a frame according to the given acknowledgment method.
void DoCtsAfterMuRtsTimeout(Ptr< WifiMpdu > muRts, const WifiTxVector &txVector, bool updateFailedCw)
Called when no CTS frame is received after an MU-RTS.
void SetMultiUserScheduler(const Ptr< MultiUserScheduler > muScheduler)
Set the Multi-user Scheduler associated with this Frame Exchange Manager.
WifiTxVector GetCtsTxVectorAfterMuRts(const CtrlTriggerHeader &trigger, uint16_t staId) const
Get the TXVECTOR that the station having the given station ID has to use to send a CTS frame after re...
void SetWifiMac(const Ptr< WifiMac > mac) override
Set the MAC layer to use.
bool m_continueTxopAfterBsrpTf
whether to continue a TXOP a SIFS after the reception of responses to a BSRP TF when TXOP limit is ze...
Ptr< MultiUserScheduler > m_muScheduler
Multi-user Scheduler (HE APs only)
virtual void CtsAfterMuRtsTimeout(Ptr< WifiMpdu > muRts, const WifiTxVector &txVector)
Called when no CTS frame is received after an MU-RTS.
void ProtectionCompleted() override
Transmit prepared frame immediately, if no protection was used, or in a SIFS, if protection was compl...
static Ptr< WifiPsdu > GetPsduTo(Mac48Address to, const WifiPsduMap &psduMap)
Get the PSDU in the given PSDU map that is addressed to the given MAC address, if any,...
Ptr< StaWifiMac > m_staMac
MAC pointer (null if not a STA)
void CtsTimeout(Ptr< WifiMpdu > rts, const WifiTxVector &txVector) override
Called when the CTS timeout expires.
EventId m_intraBssNavResetEvent
the event to reset the intra-BSS NAV after an RTS
bool StartFrameExchange(Ptr< QosTxop > edca, Time availableTime, bool initialFrame) override
Start a frame exchange (including protection frames and acknowledgment frames as needed) that fits wi...
void SendPsduMap()
Send the current PSDU map as a DL MU PPDU.
WifiPsduMap m_psduMap
the A-MPDU being transmitted
static TypeId GetTypeId()
Get the type ID.
Time m_intraBssNavEnd
intra-BSS NAV expiration time
std::set< Mac48Address > GetTfRecipients(const CtrlTriggerHeader &trigger) const
Get the (link) address of the non-AP stations solicited by the given Trigger Frame.
EventId m_multiStaBaEvent
Sending a Multi-STA BlockAck event.
void SendPsduMapWithProtection(WifiPsduMap psduMap, WifiTxParameters &txParams)
Send a map of PSDUs as a DL MU PPDU.
void StartProtection(const WifiTxParameters &txParams) override
Start the protection mechanism indicated by the given TX parameters.
void TransmissionSucceeded() override
Take necessary actions upon a transmission success.
static Time ConvertLSigLengthToHeTbPpduDuration(uint16_t length, const WifiTxVector &txVector, WifiPhyBand band)
Definition he-phy.cc:280
Ptr< WifiMpdu > GetBar(AcIndex ac, std::optional< uint8_t > optTid=std::nullopt, std::optional< Mac48Address > optAddress=std::nullopt)
Get the next BlockAckRequest or MU-BAR Trigger Frame to send, if any.
void ForwardMpduDown(Ptr< WifiMpdu > mpdu, WifiTxVector &txVector) override
Forward an MPDU down to the PHY layer.
bool StartFrameExchange(Ptr< QosTxop > edca, Time availableTime, bool initialFrame) override
Start a frame exchange (including protection frames and acknowledgment frames as needed) that fits wi...
void ReleaseSequenceNumbers(Ptr< const WifiPsdu > psdu) const override
Make the sequence numbers of MPDUs included in the given PSDU available again if the MPDUs have never...
an EUI-48 address
static Mac48Address GetBroadcast()
bool IsBroadcast() const
A tag to be attached to a response to a multi-user UL frame, that carries the SNR values with which t...
Definition mu-snr-tag.h:26
double Get(uint16_t staId) const
Return the SNR value for the given sender.
Definition mu-snr-tag.cc:53
TxFormat
Enumeration of the possible transmission formats.
virtual void DoDispose()
Destructor implementation.
Definition object.cc:433
Smart pointer class similar to boost::intrusive_ptr.
Ptr< QosTxop > m_edca
the EDCAF that gained channel access
void TransmissionFailed(bool forceCurrentCw=false) override
Take necessary actions upon a transmission failure.
virtual bool SendCfEndIfNeeded()
Send a CF-End frame to indicate the completion of the TXOP, provided that the remaining duration is l...
virtual Time GetRemainingTxop(uint8_t linkId) const
Return the remaining duration in the current TXOP on the given link.
Definition qos-txop.cc:637
static EventId Schedule(const Time &delay, FUNC f, Ts &&... args)
Schedule an event to expire after delay.
Definition simulator.h:560
static Time Now()
Return the current simulation virtual time.
Definition simulator.cc:197
Introspection did not find any typical Config paths.
Definition snr-tag.h:24
double Get() const
Return the SNR value.
Definition snr-tag.cc:79
Simulation virtual time values and global simulation resolution.
Definition nstime.h:94
TimeWithUnit As(const Unit unit=Time::AUTO) const
Attach a unit to a Time, to facilitate output in a specific unit.
Definition time.cc:404
bool IsStrictlyPositive() const
Exactly equivalent to t > 0.
Definition nstime.h:340
@ MS
millisecond
Definition nstime.h:106
bool IsZero() const
Exactly equivalent to t == 0.
Definition nstime.h:304
Time GetTxopLimit() const
Return the TXOP limit.
Definition txop.cc:602
a unique identifier for an interface.
Definition type-id.h:48
TypeId SetParent(TypeId tid)
Set the parent TypeId.
Definition type-id.cc:1001
VhtFrameExchangeManager handles the frame exchange sequences for VHT stations.
Ptr< WifiPsdu > GetWifiPsdu(Ptr< WifiMpdu > mpdu, const WifiTxVector &txVector) const override
Get a PSDU containing the given MPDU.
static void SetQosAckPolicy(Ptr< WifiMpdu > item, const WifiAcknowledgment *acknowledgment)
Set the QoS Ack policy for the given MPDU, which must be a QoS data frame.
Implements the IEEE 802.11 MAC header.
uint8_t GetQosTid() const
Return the Traffic ID of a QoS header.
bool IsAck() const
Return true if the header is an Ack header.
bool IsBlockAckReq() const
Return true if the header is a BlockAckRequest header.
bool IsCts() const
Return true if the header is a CTS header.
Mac48Address GetAddr1() const
Return the address in the Address 1 field.
bool IsTrigger() const
Return true if the header is a Trigger header.
void SetNoMoreFragments()
Un-set the More Fragment bit in the Frame Control Field.
bool IsCtl() const
Return true if the Type is Control.
Time GetDuration() const
Return the duration from the Duration/ID field (Time object).
void SetSequenceNumber(uint16_t seq)
Set the sequence number of the header.
virtual uint32_t GetSize() const
Return the size of the WifiMacHeader in octets.
void SetDsNotFrom()
Un-set the From DS bit in the Frame Control field.
void SetAddr1(Mac48Address address)
Fill the Address 1 field with the given address.
bool IsBlockAck() const
Return true if the header is a BlockAck header.
virtual void SetType(WifiMacType type, bool resetToDsFromDs=true)
Set Type/Subtype values with the correct values depending on the given type.
Mac48Address GetAddr2() const
Return the address in the Address 2 field.
bool HasData() const
Return true if the header type is DATA and is not DATA_NULL.
void SetQosTid(uint8_t tid)
Set the TID for the QoS header.
QosAckPolicy GetQosAckPolicy() const
Return the QoS Ack policy in the QoS control field.
void SetDuration(Time duration)
Set the Duration/ID field with the given duration (Time object).
void SetDsTo()
Set the To DS bit in the Frame Control field.
void SetAddr2(Mac48Address address)
Fill the Address 2 field with the given address.
bool IsQosData() const
Return true if the Type is DATA and Subtype is one of the possible values for QoS Data.
void SetQosEosp()
Set the end of service period (EOSP) bit in the QoS control field.
void SetAddr3(Mac48Address address)
Fill the Address 3 field with the given address.
void SetDsNotTo()
Un-set the To DS bit in the Frame Control field.
void SetNoRetry()
Un-set the Retry bit in the Frame Control field.
represent a single transmission mode
Definition wifi-mode.h:40
Time GetSlot() const
Return the slot duration for this PHY.
Definition wifi-phy.cc:841
Time GetSifs() const
Return the Short Interframe Space (SIFS) for this PHY.
Definition wifi-phy.cc:829
static Time CalculateTxDuration(uint32_t size, const WifiTxVector &txVector, WifiPhyBand band, uint16_t staId=SU_STA_ID)
Definition wifi-phy.cc:1587
WifiPhyBand GetPhyBand() const
Get the configured Wi-Fi band.
Definition wifi-phy.cc:1069
static Time CalculatePhyPreambleAndHeaderDuration(const WifiTxVector &txVector)
Definition wifi-phy.cc:1580
This class stores the TX parameters (TX vector, protection mechanism, acknowledgment mechanism,...
std::optional< Time > m_txDuration
TX duration of the frame.
std::unique_ptr< WifiProtection > m_protection
protection method
uint32_t GetSize(Mac48Address receiver) const
Get the size in bytes of the (A-)MPDU addressed to the given receiver.
std::unique_ptr< WifiAcknowledgment > m_acknowledgment
acknowledgment method
const PsduInfo * GetPsduInfo(Mac48Address receiver) const
Get a pointer to the information about the PSDU addressed to the given receiver, if present,...
void UndoAddMpdu()
Undo the addition of the last MPDU added by calling AddMpdu().
WifiTxVector m_txVector
TXVECTOR of the frame being prepared.
void AddMpdu(Ptr< const WifiMpdu > mpdu)
Record that an MPDU is being added to the current frame.
void Clear()
Reset the TX parameters.
bool IsRunning() const
Return true if the timer is running.
Reason
The reason why the timer was started.
const std::set< Mac48Address > & GetStasExpectedToRespond() const
void Set(Reason reason, const Time &delay, const std::set< Mac48Address > &from, MEM mem_ptr, OBJ obj, Args... args)
This method is called when a frame soliciting a response is transmitted.
Reason GetReason() const
Get the reason why the timer was started.
This class mimics the TXVECTOR which is to be passed to the PHY in order to define the parameters whi...
void SetTxPowerLevel(uint8_t powerlevel)
Sets the selected transmission power level.
uint8_t GetBssColor() const
Get the BSS color.
void SetGuardInterval(Time guardInterval)
Sets the guard interval duration (in nanoseconds)
void SetTriggerResponding(bool triggerResponding)
Set the Trigger Responding parameter to the given value.
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.
void SetHeMuUserInfo(uint16_t staId, HeMuUserInfo userInfo)
Set the HE MU user-specific transmission information for the given STA-ID.
WifiPreamble GetPreambleType() const
void SetAggregation(bool aggregation)
Sets if PSDU contains A-MPDU.
void SetChannelWidth(MHz_u channelWidth)
Sets the selected channelWidth.
const HeMuUserInfoMap & GetHeMuUserInfoMap() const
Get a const reference to the map HE MU user-specific transmission information indexed by STA-ID.
WifiModulationClass GetModulationClass() const
Get the modulation class specified by this TXVECTOR.
void SetLength(uint16_t length)
Set the LENGTH field of the L-SIG.
MHz_u GetChannelWidth() const
void SetSigBMode(const WifiMode &mode)
Set the MCS used for SIG-B.
void SetBssColor(uint8_t color)
Set the BSS color.
void SetMode(WifiMode mode)
Sets the selected payload transmission mode.
void SetPreambleType(WifiPreamble preamble)
Sets the preamble type.
#define NS_ASSERT(condition)
At runtime, in debugging builds, if this condition is not true, the program prints the source file,...
Definition assert.h:55
#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
Ptr< const AttributeChecker > MakeBooleanChecker()
Definition boolean.cc:113
Ptr< const AttributeAccessor > MakeBooleanAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition boolean.h:70
#define NS_ABORT_MSG(msg)
Unconditional abnormal program termination with a message.
Definition abort.h:38
#define NS_ABORT_MSG_IF(cond, msg)
Abnormal program termination if a condition is true, with a message.
Definition abort.h:97
#define NS_ABORT_IF(cond)
Abnormal program termination if a condition is true.
Definition abort.h:65
#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_LOGIC(msg)
Use NS_LOG to output a message of level LOG_LOGIC.
Definition log.h:271
#define NS_LOG_FUNCTION_NOARGS()
Output the name of the function.
#define NS_LOG_FUNCTION(parameters)
If log level LOG_FUNCTION is enabled, this macro will output all input parameters separated by ",...
#define NS_LOG_WARN(msg)
Use NS_LOG to output a message of level LOG_WARN.
Definition log.h:250
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition log.h:264
#define NS_OBJECT_ENSURE_REGISTERED(type)
Register an Object subclass with the TypeId system.
Definition object-base.h:35
Ptr< T > Create(Ts &&... args)
Create class instances by constructors with varying numbers of arguments and return them by Ptr.
Definition ptr.h:436
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition nstime.h:1344
AcIndex
This enumeration defines the Access Categories as an enumeration with values corresponding to the AC ...
Definition qos-utils.h:62
@ WIFI_PREAMBLE_EHT_TB
@ WIFI_PREAMBLE_HE_TB
@ WIFI_PHY_BAND_2_4GHZ
The 2.4 GHz band.
@ WIFI_MOD_CLASS_VHT
VHT (Clause 22)
Declaration of ns3::HePhy class and ns3::HeSigAParameters struct.
void(* Time)(Time oldValue, Time newValue)
TracedValue callback signature for Time.
Definition nstime.h:864
Every class exported by the ns3 library is enclosed in the ns3 namespace.
U * PeekPointer(const Ptr< U > &p)
Definition ptr.h:443
std::unordered_map< uint16_t, Ptr< const WifiPsdu > > WifiConstPsduMap
Map of const PSDUs indexed by STA-ID.
bool IsTrigger(const WifiPsduMap &psduMap)
Ptr< T1 > DynamicCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:580
uint32_t GetBlockAckRequestSize(BlockAckReqType type)
Return the total BlockAckRequest size (including FCS trailer).
Definition wifi-utils.cc:68
uint32_t GetMuBarSize(std::list< BlockAckReqType > types)
Return the total MU-BAR size (including FCS trailer).
Definition wifi-utils.cc:78
const std::map< AcIndex, WifiAc > wifiAcList
Map containing the four ACs in increasing order of priority (according to Table 10-1 "UP-to-AC Mappin...
Definition qos-utils.cc:115
@ WIFI_MAC_CTL_TRIGGER
@ WIFI_MAC_CTL_RTS
@ WIFI_MAC_CTL_BACKRESP
@ WIFI_MAC_QOSDATA_NULL
uint32_t GetBlockAckSize(BlockAckType type)
Return the total BlockAck size (including FCS trailer).
Definition wifi-utils.cc:58
bool IsDlMu(WifiPreamble preamble)
Return true if a preamble corresponds to a downlink multi-user transmission.
uint32_t GetAckSize()
Return the total Ack size (including FCS trailer).
Definition wifi-utils.cc:50
static constexpr uint16_t SU_STA_ID
STA_ID to identify a single user (SU)
Definition wifi-mode.h:24
uint32_t GetCtsSize()
Return the total CTS size (including FCS trailer).
std::unordered_map< uint16_t, Ptr< WifiPsdu > > WifiPsduMap
Map of PSDUs indexed by STA-ID.
ns3::Time timeout
std::vector< uint8_t > m_bitmapLen
Length (bytes) of included bitmaps.
RxSignalInfo structure containing info on the received signal.
Definition wifi-types.h:72
double snr
SNR in linear scale.
Definition wifi-types.h:73
WifiAcknowledgment is an abstract base struct.
const Method method
acknowledgment method
std::optional< Time > acknowledgmentTime
time required by the acknowledgment method
WifiDlMuAggregateTf specifies that a DL MU PPDU made of PSDUs including each a MU-BAR Trigger Frame i...
std::map< Mac48Address, BlockAckInfo > stationsReplyingWithBlockAck
Set of stations replying with a BlockAck frame.
WifiDlMuBarBaSequence specifies that a DL MU PPDU is acknowledged through a sequence of BlockAckReq a...
WifiDlMuTfMuBar specifies that a DL MU PPDU is followed after a SIFS duration by a MU-BAR Trigger Fra...
WifiMuRtsCtsProtection specifies that MU-RTS/CTS protection method is used.
WifiNoAck specifies that no acknowledgment is required.
WifiNoProtection specifies that no protection method is used.
WifiProtection is an abstract base struct.
const Method method
protection method
WifiUlMuMultiStaBa specifies that a Basic Trigger Frame is being sent to solicit TB PPDUs that will b...
BlockAckType baType
BlockAck type.
std::map< std::pair< Mac48Address, uint8_t >, std::size_t > stationsReceivingMultiStaBa
Map (originator, tid) pairs to the their index in baType.