A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
he-ppdu.cc
Go to the documentation of this file.
1/*
2 * Copyright (c) 2020 Orange Labs
3 *
4 * SPDX-License-Identifier: GPL-2.0-only
5 *
6 * Author: Rediet <getachew.redieteab@orange.com>
7 * Muhammad Iqbal Rochman <muhiqbalcr@uchicago.edu>
8 * Sébastien Deronne <sebastien.deronne@gmail.com> (HeSigHeader)
9 */
10
11#include "he-ppdu.h"
12
13#include "he-phy.h"
14
15#include "ns3/log.h"
16#include "ns3/wifi-phy-operating-channel.h"
17#include "ns3/wifi-phy.h"
18#include "ns3/wifi-psdu.h"
19#include "ns3/wifi-utils.h"
20
21#include <algorithm>
22#include <numeric>
23
24namespace ns3
25{
26
28
29std::ostream&
30operator<<(std::ostream& os, const HePpdu::TxPsdFlag& flag)
31{
32 switch (flag)
33 {
35 return (os << "PSD_NON_HE_PORTION");
37 return (os << "PSD_HE_PORTION");
38 default:
39 NS_FATAL_ERROR("Invalid PSD flag");
40 return (os << "INVALID");
41 }
42}
43
45 const WifiTxVector& txVector,
46 const WifiPhyOperatingChannel& channel,
47 Time ppduDuration,
48 uint64_t uid,
49 TxPsdFlag flag,
50 bool instantiateHeaders /* = true */)
51 : OfdmPpdu(psdus.begin()->second,
52 txVector,
53 channel,
54 uid,
55 false), // don't instantiate LSigHeader of OfdmPpdu
56 m_txPsdFlag(flag)
57{
58 NS_LOG_FUNCTION(this << psdus << txVector << channel << ppduDuration << uid << flag
59 << instantiateHeaders);
60
61 // overwrite with map (since only first element used by OfdmPpdu)
62 m_psdus.begin()->second = nullptr;
63 m_psdus.clear();
64 m_psdus = psdus;
65 if (instantiateHeaders)
66 {
67 SetPhyHeaders(txVector, ppduDuration);
68 }
69}
70
72 const WifiTxVector& txVector,
73 const WifiPhyOperatingChannel& channel,
74 Time ppduDuration,
75 uint64_t uid,
76 bool instantiateHeaders /* = true */)
77 : OfdmPpdu(psdu,
78 txVector,
79 channel,
80 uid,
81 false), // don't instantiate LSigHeader of OfdmPpdu
82 m_txPsdFlag(PSD_NON_HE_PORTION)
83{
84 NS_LOG_FUNCTION(this << psdu << txVector << channel << ppduDuration << uid
85 << instantiateHeaders);
86 NS_ASSERT(!IsMu());
87 if (instantiateHeaders)
88 {
89 SetPhyHeaders(txVector, ppduDuration);
90 }
91}
92
93void
94HePpdu::SetPhyHeaders(const WifiTxVector& txVector, Time ppduDuration)
95{
96 NS_LOG_FUNCTION(this << txVector << ppduDuration);
97 SetLSigHeader(ppduDuration);
98 SetHeSigHeader(txVector);
99}
100
101void
103{
104 uint8_t sigExtension = 0;
107 {
108 sigExtension = 6;
109 }
110 uint8_t m = IsDlMu() ? 1 : 2;
111 uint16_t length = ((ceil((static_cast<double>(ppduDuration.GetNanoSeconds() - (20 * 1000) -
112 (sigExtension * 1000)) /
113 1000) /
114 4.0) *
115 3) -
116 3 - m);
117 m_lSig.SetLength(length);
118}
119
120void
122{
123 const auto bssColor = txVector.GetBssColor();
124 NS_ASSERT(bssColor < 64);
126 {
128 .m_bssColor = bssColor,
129 .m_bandwidth = GetChannelWidthEncodingFromMhz(txVector.GetChannelWidth())});
130 }
131 else if (ns3::IsDlMu(m_preamble))
132 {
133 const auto p20Index = m_operatingChannel.GetPrimaryChannelIndex(MHz_u{20});
134 const uint8_t noMuMimoUsers{0};
136 .m_bssColor = bssColor,
137 .m_bandwidth = GetChannelWidthEncodingFromMhz(txVector.GetChannelWidth()),
138 .m_sigBMcs = txVector.GetSigBMode().GetMcsValue(),
139 .m_muMimoUsers = (txVector.IsSigBCompression()
140 ? GetMuMimoUsersEncoding(txVector.GetHeMuUserInfoMap().size())
141 : noMuMimoUsers),
142 .m_sigBCompression = txVector.IsSigBCompression(),
143 .m_giLtfSize = GetGuardIntervalAndNltfEncoding(txVector.GetGuardInterval(),
144 2 /*NLTF currently unused*/),
145 .m_ruAllocation = txVector.GetRuAllocation(p20Index),
146 .m_contentChannels = GetHeSigBContentChannels(txVector, p20Index),
147 .m_center26ToneRuIndication =
148 (txVector.GetChannelWidth() >= MHz_u{80})
149 ? std::optional{txVector.GetCenter26ToneRuIndication()}
150 : std::nullopt});
151 }
152 else
153 {
154 const auto mcs = txVector.GetMode().GetMcsValue();
155 NS_ASSERT(mcs <= 11);
157 .m_bssColor = bssColor,
158 .m_mcs = mcs,
159 .m_bandwidth = GetChannelWidthEncodingFromMhz(txVector.GetChannelWidth()),
160 .m_giLtfSize = GetGuardIntervalAndNltfEncoding(txVector.GetGuardInterval(),
161 2 /*NLTF currently unused*/),
162 .m_nsts = GetNstsEncodingFromNss(txVector.GetNss())});
163 }
164}
165
168{
169 WifiTxVector txVector;
170 txVector.SetPreambleType(m_preamble);
172 return txVector;
173}
174
175void
177{
178 txVector.SetLength(m_lSig.GetLength());
179 txVector.SetAggregation(m_psdus.size() > 1 || m_psdus.begin()->second->IsAggregate());
180 if (!IsMu())
181 {
182 auto heSigHeader = std::get_if<HeSuSigHeader>(&m_heSig);
183 NS_ASSERT(heSigHeader && (heSigHeader->m_format == 1));
184 txVector.SetMode(HePhy::GetHeMcs(heSigHeader->m_mcs));
185 txVector.SetNss(GetNssFromNstsEncoding(heSigHeader->m_nsts));
186 txVector.SetChannelWidth(GetChannelWidthMhzFromEncoding(heSigHeader->m_bandwidth));
187 txVector.SetGuardInterval(GetGuardIntervalFromEncoding(heSigHeader->m_giLtfSize));
188 txVector.SetBssColor(heSigHeader->m_bssColor);
189 }
190 else if (IsUlMu())
191 {
192 auto heSigHeader = std::get_if<HeTbSigHeader>(&m_heSig);
193 NS_ASSERT(heSigHeader && (heSigHeader->m_format == 0));
194 txVector.SetChannelWidth(GetChannelWidthMhzFromEncoding(heSigHeader->m_bandwidth));
195 txVector.SetBssColor(heSigHeader->m_bssColor);
196 }
197 else if (IsDlMu())
198 {
199 auto heSigHeader = std::get_if<HeMuSigHeader>(&m_heSig);
200 NS_ASSERT(heSigHeader);
201 txVector.SetChannelWidth(GetChannelWidthMhzFromEncoding(heSigHeader->m_bandwidth));
202 txVector.SetGuardInterval(GetGuardIntervalFromEncoding(heSigHeader->m_giLtfSize));
203 txVector.SetBssColor(heSigHeader->m_bssColor);
204 SetHeMuUserInfos(txVector,
205 heSigHeader->m_ruAllocation,
206 heSigHeader->m_center26ToneRuIndication,
207 heSigHeader->m_contentChannels,
208 heSigHeader->m_sigBCompression,
209 GetMuMimoUsersFromEncoding(heSigHeader->m_muMimoUsers));
210 txVector.SetSigBMode(HePhy::GetVhtMcs(heSigHeader->m_sigBMcs));
211 const auto p20Index = m_operatingChannel.GetPrimaryChannelIndex(MHz_u{20});
212 txVector.SetRuAllocation(heSigHeader->m_ruAllocation, p20Index);
213 if (heSigHeader->m_center26ToneRuIndication.has_value())
214 {
215 txVector.SetCenter26ToneRuIndication(heSigHeader->m_center26ToneRuIndication.value());
216 }
217 if (heSigHeader->m_sigBCompression)
218 {
219 NS_ASSERT(GetMuMimoUsersFromEncoding(heSigHeader->m_muMimoUsers) ==
220 txVector.GetHeMuUserInfoMap().size());
221 }
222 }
223}
224
226HePpdu::GetRuSpec(std::size_t ruAllocIndex,
227 const std::vector<WifiRu::RuSpec>& ruSpecs,
228 RuType ruType,
229 std::size_t ruIndex,
230 MHz_u bw) const
231{
232 const auto ruBw = WifiRu::GetBandwidth(ruType);
233 const uint8_t num20MhzSubchannelsInRu = (ruBw < MHz_u{20}) ? 1 : Count20MHzSubchannels(ruBw);
234 const std::size_t numRus = (ruBw > MHz_u{20}) ? 1 : HeRu::GetNRus(MHz_u{20}, ruType);
235 const std::size_t ruIndexOffset =
236 (ruBw < MHz_u{20}) ? (numRus * ruAllocIndex) : (ruAllocIndex / num20MhzSubchannelsInRu);
237 std::size_t index = WifiRu::GetIndex(ruSpecs.at(ruIndex)) + ruIndexOffset;
238 auto isPrimary80 = true;
239 if (bw > MHz_u{80})
240 {
241 const auto isLow80 = ruAllocIndex < 4;
242 const auto p20Index = m_operatingChannel.GetPrimaryChannelIndex(MHz_u{20});
243 const auto primary80IsLower80 = (p20Index < bw / MHz_u{40});
244 if (!isLow80)
245 {
246 const auto numRusP80 = HeRu::GetRusOfType(MHz_u{80}, ruType).size();
247 index -= (ruType == RuType::RU_26_TONE) ? (numRusP80 - 1) : numRusP80;
248 }
249 isPrimary80 = ((primary80IsLower80 && isLow80) || (!primary80IsLower80 && !isLow80));
250 }
251 if ((ruType == RuType::RU_26_TONE) && (ruAllocIndex >= 2) && (index >= 19))
252 {
253 index++;
254 }
255 return HeRu::RuSpec{ruType, index, isPrimary80};
256}
257
258void
260 const RuAllocation& ruAllocation,
261 std::optional<Center26ToneRuIndication> center26ToneRuIndication,
262 const HeSigBContentChannels& contentChannels,
263 bool sigBcompression,
264 uint8_t numMuMimoUsers) const
265{
266 NS_ASSERT(ruAllocation.size() == Count20MHzSubchannels(txVector.GetChannelWidth()));
267 std::vector<uint8_t> remainingRuAllocIndices(ruAllocation.size());
268 std::iota(remainingRuAllocIndices.begin(), remainingRuAllocIndices.end(), 0);
269 std::size_t contentChannelIndex = 0;
270 std::size_t ruAllocIndex = 0;
271 for (const auto& contentChannel : contentChannels)
272 {
273 std::size_t numRusLeft = 0;
274 std::size_t numUsersLeft = 0;
275 ruAllocIndex = remainingRuAllocIndices.front();
276 std::size_t numUsersLeftInCc = contentChannel.size();
277 if (contentChannel.empty())
278 {
279 const auto pos = std::find(remainingRuAllocIndices.cbegin(),
280 remainingRuAllocIndices.cend(),
281 ruAllocIndex);
282 remainingRuAllocIndices.erase(pos);
283 ++contentChannelIndex;
284 continue;
285 }
286 for (const auto& userInfo : contentChannel)
287 {
288 if (center26ToneRuIndication && (numUsersLeftInCc == 1))
289 {
290 // handle central 26 tones
291 if ((contentChannelIndex == 0) &&
292 ((*center26ToneRuIndication ==
294 (*center26ToneRuIndication ==
296 {
297 txVector.SetHeMuUserInfo(
298 userInfo.staId,
299 {HeRu::RuSpec{RuType::RU_26_TONE, 19, true}, userInfo.mcs, userInfo.nss});
300 continue;
301 }
302 else if ((contentChannelIndex == 1) &&
303 ((*center26ToneRuIndication ==
305 (*center26ToneRuIndication ==
308 {
309 txVector.SetHeMuUserInfo(
310 userInfo.staId,
311 {HeRu::RuSpec{RuType::RU_26_TONE, 19, false}, userInfo.mcs, userInfo.nss});
312 continue;
313 }
314 }
315 NS_ASSERT(ruAllocIndex < ruAllocation.size());
316 const auto mc{WIFI_MOD_CLASS_HE};
317 auto ruSpecs = WifiRu::GetRuSpecs(ruAllocation.at(ruAllocIndex), mc);
318 while (ruSpecs.empty() && (ruAllocIndex < ruAllocation.size()))
319 {
320 const auto pos = std::find(remainingRuAllocIndices.cbegin(),
321 remainingRuAllocIndices.cend(),
322 ruAllocIndex);
323 remainingRuAllocIndices.erase(pos);
324 ruAllocIndex += 2;
325 NS_ASSERT(ruAllocIndex < ruAllocation.size());
326 ruSpecs = WifiRu::GetRuSpecs(ruAllocation.at(ruAllocIndex), mc);
327 }
328 if (numRusLeft == 0)
329 {
330 numRusLeft = ruSpecs.size();
331 }
332 if (numUsersLeft == 0)
333 {
334 if (sigBcompression)
335 {
336 numUsersLeft = numMuMimoUsers;
337 }
338 else
339 {
340 // not MU-MIMO
341 numUsersLeft = 1;
342 }
343 }
344 auto ruIndex = (ruSpecs.size() - numRusLeft);
345 const auto ruSpec = ruSpecs.at(ruIndex);
346 auto ruType = WifiRu::GetRuType(ruSpec);
347 if (sigBcompression)
348 {
349 ruType = WifiRu::GetRuType(ruAllocation.size() * MHz_u{20});
350 }
351 if (userInfo.staId != NO_USER_STA_ID)
352 {
353 const auto ru{
354 GetRuSpec(ruAllocIndex, ruSpecs, ruType, ruIndex, txVector.GetChannelWidth())};
355 txVector.SetHeMuUserInfo(userInfo.staId, {ru, userInfo.mcs, userInfo.nss});
356 }
357 numRusLeft--;
358 numUsersLeft--;
359 numUsersLeftInCc--;
360 if (numRusLeft == 0 && numUsersLeft == 0)
361 {
362 const auto ruBw = WifiRu::GetBandwidth(ruType);
363 const uint8_t num20MhzSubchannelsInRu =
364 (ruBw < MHz_u{20}) ? 1 : Count20MHzSubchannels(ruBw);
365 const auto pos = std::find(remainingRuAllocIndices.cbegin(),
366 remainingRuAllocIndices.cend(),
367 ruAllocIndex);
368 remainingRuAllocIndices.erase(pos);
369 ruAllocIndex += num20MhzSubchannelsInRu;
370 if (ruAllocIndex % 2 != contentChannelIndex)
371 {
372 ++ruAllocIndex;
373 }
374 }
375 }
376 contentChannelIndex++;
377 }
378}
379
380Time
381HePpdu::GetTxDuration() const
382{
383 Time ppduDuration;
384 const auto& txVector = GetTxVector();
385 const auto length = m_lSig.GetLength();
386 const auto tSymbol = HePhy::GetSymbolDuration(txVector.GetGuardInterval());
387 const auto preambleDuration = WifiPhy::CalculatePhyPreambleAndHeaderDuration(txVector);
388 NS_ASSERT(m_operatingChannel.IsSet());
389 uint8_t sigExtension = (m_operatingChannel.GetPhyBand() == WIFI_PHY_BAND_2_4GHZ) ? 6 : 0;
390 uint8_t m = IsDlMu() ? 1 : 2;
391 // Equation 27-11 of IEEE P802.11ax/D4.0
392 const auto calculatedDuration =
393 MicroSeconds(((ceil(static_cast<double>(length + 3 + m) / 3)) * 4) + 20 + sigExtension);
394 NS_ASSERT(calculatedDuration > preambleDuration);
395 uint32_t nSymbols =
396 floor(static_cast<double>((calculatedDuration - preambleDuration).GetNanoSeconds() -
397 (sigExtension * 1000)) /
398 tSymbol.GetNanoSeconds());
399 return (preambleDuration + (nSymbols * tSymbol) + MicroSeconds(sigExtension));
400}
401
403HePpdu::Copy() const
404{
405 return Ptr<WifiPpdu>(new HePpdu(*this), false);
406}
407
409HePpdu::GetType() const
410{
411 switch (m_preamble)
412 {
417 default:
418 return WIFI_PPDU_TYPE_SU;
419 }
420}
421
422bool
423HePpdu::IsMu() const
424{
425 return (IsDlMu() || IsUlMu());
426}
427
428bool
429HePpdu::IsDlMu() const
430{
431 return (m_preamble == WIFI_PREAMBLE_HE_MU);
432}
433
434bool
435HePpdu::IsUlMu() const
436{
437 return (m_preamble == WIFI_PREAMBLE_HE_TB);
438}
439
441HePpdu::GetPsdu(uint8_t bssColor, uint16_t staId /* = SU_STA_ID */) const
442{
443 if (!IsMu())
444 {
445 NS_ASSERT(m_psdus.size() == 1);
446 return m_psdus.at(SU_STA_ID);
447 }
448
449 if (IsUlMu())
450 {
451 auto heSigHeader = std::get_if<HeTbSigHeader>(&m_heSig);
452 NS_ASSERT(heSigHeader);
453 NS_ASSERT(m_psdus.size() == 1);
454 if ((bssColor == 0) || (heSigHeader->m_bssColor == 0) ||
455 (bssColor == heSigHeader->m_bssColor))
456 {
457 return m_psdus.cbegin()->second;
458 }
459 }
460 else
461 {
462 auto heSigHeader = std::get_if<HeMuSigHeader>(&m_heSig);
463 NS_ASSERT(heSigHeader);
464 if ((bssColor == 0) || (heSigHeader->m_bssColor == 0) ||
465 (bssColor == heSigHeader->m_bssColor))
466 {
467 const auto it = m_psdus.find(staId);
468 if (it != m_psdus.cend())
469 {
470 return it->second;
471 }
472 }
473 }
474 return nullptr;
475}
476
477uint16_t
478HePpdu::GetStaId() const
479{
480 NS_ASSERT(IsUlMu());
481 return m_psdus.begin()->first;
482}
483
484MHz_u
485HePpdu::GetTxChannelWidth() const
486{
487 if (const auto& txVector = GetTxVector();
488 txVector.IsValid() && txVector.IsUlMu() && GetStaId() != SU_STA_ID)
489 {
490 TxPsdFlag flag = GetTxPsdFlag();
491 const auto ruWidth = WifiRu::GetBandwidth(WifiRu::GetRuType(txVector.GetRu(GetStaId())));
492 MHz_u channelWidth =
493 (flag == PSD_NON_HE_PORTION && ruWidth < MHz_u{20}) ? MHz_u{20} : ruWidth;
494 NS_LOG_INFO("Use " << channelWidth << " MHz for TB PPDU from " << GetStaId() << " for "
495 << flag);
496 return channelWidth;
497 }
498 else
499 {
500 return OfdmPpdu::GetTxChannelWidth();
501 }
502}
503
505HePpdu::GetTxPsdFlag() const
506{
507 return m_txPsdFlag;
508}
509
510void
511HePpdu::SetTxPsdFlag(TxPsdFlag flag) const
512{
513 NS_LOG_FUNCTION(this << flag);
514 m_txPsdFlag = flag;
515}
516
517void
518HePpdu::UpdateTxVectorForUlMu(const std::optional<WifiTxVector>& trigVector) const
519{
520 if (trigVector.has_value())
521 {
522 NS_LOG_FUNCTION(this << trigVector.value());
523 }
524 else
525 {
526 NS_LOG_FUNCTION(this);
527 }
528 if (!m_txVector.has_value())
529 {
530 m_txVector = GetTxVector();
531 }
532 NS_ASSERT(GetModulation() >= WIFI_MOD_CLASS_HE);
533 NS_ASSERT(GetType() == WIFI_PPDU_TYPE_UL_MU);
534 // HE TB PPDU reception needs information from the TRIGVECTOR to be able to receive the PPDU
535 const auto staId = GetStaId();
536 if (trigVector.has_value() && trigVector->IsUlMu() &&
537 (trigVector->GetHeMuUserInfoMap().contains(staId)))
538 {
539 // These information are not carried in HE-SIG-A for a HE TB PPDU,
540 // but they are carried in the Trigger frame soliciting the HE TB PPDU
541 m_txVector->SetGuardInterval(trigVector->GetGuardInterval());
542 m_txVector->SetHeMuUserInfo(staId, trigVector->GetHeMuUserInfo(staId));
543 }
544 else
545 {
546 // Set dummy user info, PPDU will be dropped later after decoding PHY headers.
547 m_txVector->SetHeMuUserInfo(
548 staId,
549 {HeRu::RuSpec{(WifiRu::GetRuType(m_txVector->GetChannelWidth())), 1, true}, 0, 1});
550 }
551}
552
553std::pair<std::size_t, std::size_t>
554HePpdu::GetNumRusPerHeSigBContentChannel(
555 MHz_u channelWidth,
556 const RuAllocation& ruAllocation,
557 std::optional<Center26ToneRuIndication> center26ToneRuIndication,
558 bool sigBCompression,
559 uint8_t numMuMimoUsers)
560{
561 std::pair<std::size_t /* number of RUs in content channel 1 */,
562 std::size_t /* number of RUs in content channel 2 */>
563 chSize{0, 0};
564
565 if (sigBCompression)
566 {
567 // If the HE-SIG-B Compression field in the HE-SIG-A field of an HE MU PPDU is 1,
568 // for bandwidths larger than 20 MHz, the AP performs an equitable split of
569 // the User fields between two HE-SIG-B content channels
570 if (channelWidth == MHz_u{20})
571 {
572 return {numMuMimoUsers, 0};
573 }
574 chSize.first = numMuMimoUsers / 2;
575 chSize.second = numMuMimoUsers / 2;
576 if (numMuMimoUsers != (chSize.first + chSize.second))
577 {
578 chSize.first++;
579 }
580 return chSize;
581 }
582
583 NS_ASSERT_MSG(!ruAllocation.empty(), "RU allocation is not set");
584 NS_ASSERT_MSG(ruAllocation.size() == Count20MHzSubchannels(channelWidth),
585 "RU allocation is not consistent with packet bandwidth");
586
587 const auto mc{WIFI_MOD_CLASS_HE};
588 switch (static_cast<uint16_t>(channelWidth))
589 {
590 case 40:
591 chSize.second += WifiRu::GetRuSpecs(ruAllocation[1], mc).size();
592 [[fallthrough]];
593 case 20:
594 chSize.first += WifiRu::GetRuSpecs(ruAllocation[0], mc).size();
595 break;
596 default:
597 for (std::size_t n = 0; n < Count20MHzSubchannels(channelWidth);)
598 {
599 std::size_t ccIndex;
600 const auto ruAlloc = ruAllocation.at(n);
601 std::size_t num20MHz{1};
602 const auto ruSpecs = WifiRu::GetRuSpecs(ruAlloc, mc);
603 const auto nRuSpecs = ruSpecs.size();
604 if (nRuSpecs == 1)
605 {
606 const auto ruBw = WifiRu::GetBandwidth(WifiRu::GetRuType(ruSpecs.front()));
607 num20MHz = Count20MHzSubchannels(ruBw);
608 }
609 if (nRuSpecs == 0)
610 {
611 ++n;
612 continue;
613 }
614 if (num20MHz > 1)
615 {
616 ccIndex = (chSize.first <= chSize.second) ? 0 : 1;
617 }
618 else
619 {
620 ccIndex = (n % 2 == 0) ? 0 : 1;
621 }
622 if (ccIndex == 0)
623 {
624 chSize.first += nRuSpecs;
625 }
626 else
627 {
628 chSize.second += nRuSpecs;
629 }
630 if (num20MHz > 1)
631 {
632 const auto skipNumIndices = (ccIndex == 0) ? num20MHz : num20MHz - 1;
633 n += skipNumIndices;
634 }
635 else
636 {
637 ++n;
638 }
639 }
640 break;
641 }
642 if (center26ToneRuIndication)
643 {
644 switch (*center26ToneRuIndication)
645 {
646 case Center26ToneRuIndication::CENTER_26_TONE_RU_LOW_80_MHZ_ALLOCATED:
647 chSize.first++;
648 break;
649 case Center26ToneRuIndication::CENTER_26_TONE_RU_HIGH_80_MHZ_ALLOCATED:
650 chSize.second++;
651 break;
652 case Center26ToneRuIndication::CENTER_26_TONE_RU_LOW_AND_HIGH_80_MHZ_ALLOCATED:
653 chSize.first++;
654 chSize.second++;
655 break;
656 case Center26ToneRuIndication::CENTER_26_TONE_RU_UNALLOCATED:
657 default:
658 break;
659 }
660 }
661 return chSize;
662}
663
665HePpdu::GetHeSigBContentChannels(const WifiTxVector& txVector, uint8_t p20Index)
666{
667 HeSigBContentChannels contentChannels{{}};
668
669 const auto channelWidth = txVector.GetChannelWidth();
670 if (channelWidth > MHz_u{20})
671 {
672 contentChannels.emplace_back();
673 }
674
675 std::optional<HeSigBUserSpecificField> cc1Central26ToneRu;
676 std::optional<HeSigBUserSpecificField> cc2Central26ToneRu;
677
678 const auto& orderedMap = txVector.GetUserInfoMapOrderedByRus(p20Index);
679 RuType prevRuType{RuType::RU_TYPE_MAX};
680 std::size_t prevRuIndex{0};
681 std::size_t prevCcIndex{0};
682 for (const auto& [ru, staIds] : orderedMap)
683 {
684 const auto ruType = WifiRu::GetRuType(ru);
685 auto ruIdx = WifiRu::GetIndex(ru);
686 if ((ruType == RuType::RU_26_TONE) && (ruIdx == 19))
687 {
688 NS_ASSERT(WifiRu::IsHe(ru));
689 const auto staId = *staIds.cbegin();
690 const auto& userInfo = txVector.GetHeMuUserInfo(staId);
691 if (std::get<HeRu::RuSpec>(ru).GetPrimary80MHz())
692 {
693 NS_ASSERT(!cc1Central26ToneRu);
694 cc1Central26ToneRu = HeSigBUserSpecificField{staId, userInfo.nss, userInfo.mcs};
695 }
696 else
697 {
698 NS_ASSERT(!cc2Central26ToneRu);
699 cc2Central26ToneRu = HeSigBUserSpecificField{staId, userInfo.nss, userInfo.mcs};
700 }
701 continue;
702 }
703
704 const auto ruIndex = WifiRu::GetPhyIndex(ru, channelWidth, p20Index);
705 if ((prevRuType < RuType::RU_TYPE_MAX) && (prevRuType != ruType))
706 {
707 prevRuIndex *= WifiRu::GetBandwidth(prevRuType) / WifiRu::GetBandwidth(ruType);
708 }
709 if (ruType >= RuType::RU_484_TONE)
710 {
711 for (auto staId : staIds)
712 {
713 // equal split
714 const auto ccIndex =
715 (contentChannels.at(0).size() <= contentChannels.at(1).size()) ? 0 : 1;
716 const auto& userInfo = txVector.GetHeMuUserInfo(staId);
717 NS_ASSERT(ru == userInfo.ru);
718 contentChannels[ccIndex].push_back({staId, userInfo.nss, userInfo.mcs});
719 }
720 continue;
721 }
722
723 const auto mc{WIFI_MOD_CLASS_HE};
724 const auto numRus = WifiRu::GetNRus(MHz_u{20}, ruType, mc);
725 while (prevRuIndex < ruIndex - 1)
726 {
727 std::size_t ccIndex{0};
728 if (channelWidth < MHz_u{40})
729 {
730 // only one content channel
731 ccIndex = 0;
732 }
733 else if (txVector.IsSigBCompression())
734 {
735 // equal split
736 ccIndex = (contentChannels.at(0).size() <= contentChannels.at(1).size()) ? 0 : 1;
737 }
738 else
739 {
740 ccIndex = ((prevRuIndex / numRus) % 2 == 0) ? 0 : 1;
741 }
742 const auto central26TonesRus =
743 WifiRu::GetCentral26TonesRus(channelWidth, prevRuType, mc);
744 const auto isCentral26ToneRu = std::none_of(
745 central26TonesRus.cbegin(),
746 central26TonesRus.cend(),
747 [ruIndex, channelWidth, p20Index](const auto& ruSpec) {
748 return WifiRu::GetPhyIndex(ruSpec, channelWidth, p20Index) == ruIndex;
749 });
750 if (ruType < RuType::RU_242_TONE && prevCcIndex == ccIndex &&
751 (ruType != RuType::RU_26_TONE || isCentral26ToneRu))
752 {
753 contentChannels[ccIndex].push_back({NO_USER_STA_ID, 0, 0});
754 }
755 ++prevRuIndex;
756 prevCcIndex = ccIndex;
757 }
758 prevRuIndex = ruIndex;
759 prevRuType = ruType;
760 for (auto staId : staIds)
761 {
762 const auto& userInfo = txVector.GetHeMuUserInfo(staId);
763 NS_ASSERT(ru == userInfo.ru);
764 std::size_t ccIndex{0};
765 if (channelWidth < MHz_u{40})
766 {
767 // only one content channel
768 ccIndex = 0;
769 }
770 else if (txVector.IsSigBCompression())
771 {
772 // MU-MIMO: equal split
773 ccIndex = (contentChannels.at(0).size() <= contentChannels.at(1).size()) ? 0 : 1;
774 }
775 else
776 {
777 if (ruType == RuType::RU_26_TONE && ruIdx > 19)
778 {
779 // "ignore" the center 26-tone RUs in 80 MHz channels
780 ruIdx--;
781 }
782 ccIndex = (((ruIdx - 1) / numRus) % 2 == 0) ? 0 : 1;
783 }
784 contentChannels.at(ccIndex).push_back({staId, userInfo.nss, userInfo.mcs});
785 prevCcIndex = ccIndex;
786 }
787 }
788
789 if (cc1Central26ToneRu)
790 {
791 contentChannels.at(0).push_back(*cc1Central26ToneRu);
792 }
793 if (cc2Central26ToneRu)
794 {
795 contentChannels.at(1).push_back(*cc2Central26ToneRu);
796 }
797
798 const auto isSigBCompression = txVector.IsSigBCompression();
799 if (!isSigBCompression)
800 {
801 // Add unassigned RUs
802 auto numNumRusPerHeSigBContentChannel = GetNumRusPerHeSigBContentChannel(
803 channelWidth,
804 txVector.GetRuAllocation(p20Index),
806 isSigBCompression,
807 isSigBCompression ? txVector.GetHeMuUserInfoMap().size() : 0);
808 std::size_t contentChannelIndex = 1;
809 for (auto& contentChannel : contentChannels)
810 {
811 const auto totalUsersInContentChannel = (contentChannelIndex == 1)
812 ? numNumRusPerHeSigBContentChannel.first
813 : numNumRusPerHeSigBContentChannel.second;
814 NS_ASSERT(contentChannel.size() <= totalUsersInContentChannel);
815 std::size_t unallocatedRus = totalUsersInContentChannel - contentChannel.size();
816 for (std::size_t i = 0; i < unallocatedRus; i++)
817 {
818 contentChannel.push_back({NO_USER_STA_ID, 0, 0});
819 }
820 contentChannelIndex++;
821 }
822 }
823
824 return contentChannels;
825}
826
828HePpdu::GetSigBFieldSize(MHz_u channelWidth,
829 const RuAllocation& ruAllocation,
830 std::optional<Center26ToneRuIndication> center26ToneRuIndication,
831 bool sigBCompression,
832 std::size_t numMuMimoUsers)
833{
834 // Compute the number of bits used by common field.
835 uint32_t commonFieldSize = 0;
836 if (!sigBCompression)
837 {
838 commonFieldSize = 4 /* CRC */ + 6 /* tail */;
839 if (channelWidth <= MHz_u{40})
840 {
841 commonFieldSize += 8; // only one allocation subfield
842 }
843 else
844 {
845 commonFieldSize +=
846 8 * (channelWidth / MHz_u{40}) /* one allocation field per 40 MHz */ +
847 1 /* center RU */;
848 }
849 }
850
851 auto numRusPerContentChannel = GetNumRusPerHeSigBContentChannel(channelWidth,
852 ruAllocation,
853 center26ToneRuIndication,
854 sigBCompression,
855 numMuMimoUsers);
856 auto maxNumRusPerContentChannel =
857 std::max(numRusPerContentChannel.first, numRusPerContentChannel.second);
858 auto maxNumUserBlockFields = maxNumRusPerContentChannel /
859 2; // handle last user block with single user, if any, further down
860 std::size_t userSpecificFieldSize =
861 maxNumUserBlockFields * (2 * 21 /* user fields (2 users) */ + 4 /* tail */ + 6 /* CRC */);
862 if (maxNumRusPerContentChannel % 2 != 0)
863 {
864 userSpecificFieldSize += 21 /* last user field */ + 4 /* CRC */ + 6 /* tail */;
865 }
866
867 return commonFieldSize + userSpecificFieldSize;
868}
869
870std::string
871HePpdu::PrintPayload() const
872{
873 std::ostringstream ss;
874 if (IsMu())
875 {
876 ss << m_psdus;
877 ss << ", " << m_txPsdFlag;
878 }
879 else
880 {
881 ss << "PSDU=" << m_psdus.at(SU_STA_ID) << " ";
882 }
883 return ss.str();
884}
885
886uint8_t
887HePpdu::GetChannelWidthEncodingFromMhz(MHz_u channelWidth)
888{
889 if (channelWidth == MHz_u{160})
890 {
891 return 3;
892 }
893 else if (channelWidth == MHz_u{80})
894 {
895 return 2;
896 }
897 else if (channelWidth == MHz_u{40})
898 {
899 return 1;
900 }
901 else
902 {
903 return 0;
904 }
905}
906
907MHz_u
908HePpdu::GetChannelWidthMhzFromEncoding(uint8_t bandwidth)
909{
910 if (bandwidth == 3)
911 {
912 return MHz_u{160};
913 }
914 else if (bandwidth == 2)
915 {
916 return MHz_u{80};
917 }
918 else if (bandwidth == 1)
919 {
920 return MHz_u{40};
921 }
922 else
923 {
924 return MHz_u{20};
925 }
926}
927
928uint8_t
929HePpdu::GetGuardIntervalAndNltfEncoding(Time guardInterval, uint8_t nltf)
930{
931 const auto gi = guardInterval.GetNanoSeconds();
932 if ((gi == 800) && (nltf == 1))
933 {
934 return 0;
935 }
936 else if ((gi == 800) && (nltf == 2))
937 {
938 return 1;
939 }
940 else if ((gi == 1600) && (nltf == 2))
941 {
942 return 2;
943 }
944 else
945 {
946 return 3;
947 }
948}
949
950Time
951HePpdu::GetGuardIntervalFromEncoding(uint8_t giAndNltfSize)
952{
953 if (giAndNltfSize == 3)
954 {
955 // we currently do not consider DCM nor STBC fields
956 return NanoSeconds(3200);
957 }
958 else if (giAndNltfSize == 2)
959 {
960 return NanoSeconds(1600);
961 }
962 else
963 {
964 return NanoSeconds(800);
965 }
966}
967
968uint8_t
969HePpdu::GetNstsEncodingFromNss(uint8_t nss)
970{
971 NS_ASSERT(nss <= 8);
972 return nss - 1;
973}
974
975uint8_t
976HePpdu::GetNssFromNstsEncoding(uint8_t nsts)
977{
978 return nsts + 1;
979}
980
981uint8_t
982HePpdu::GetMuMimoUsersEncoding(uint8_t nUsers)
983{
984 NS_ASSERT(nUsers <= 8);
985 return (nUsers - 1);
986}
987
988uint8_t
989HePpdu::GetMuMimoUsersFromEncoding(uint8_t encoding)
990{
991 return (encoding + 1);
992}
993
994} // namespace ns3
static WifiMode GetHeMcs(uint8_t index)
Return the HE MCS corresponding to the provided index.
Definition he-phy.cc:1550
HE PPDU (11ax)
Definition he-ppdu.h:39
HeSigHeader m_heSig
the HE-SIG PHY header
Definition he-ppdu.h:384
WifiTxVector DoGetTxVector() const override
Get the TXVECTOR used to send the PPDU.
Definition he-ppdu.cc:167
virtual void SetTxVectorFromPhyHeaders(WifiTxVector &txVector) const
Fill in the TXVECTOR from PHY headers.
Definition he-ppdu.cc:176
TxPsdFlag
The transmit power spectral density flag, namely used to correctly build PSDs for pre-HE and HE porti...
Definition he-ppdu.h:104
@ PSD_HE_PORTION
HE portion of an HE PPDU.
Definition he-ppdu.h:106
@ PSD_NON_HE_PORTION
Non-HE portion of an HE PPDU.
Definition he-ppdu.h:105
virtual bool IsDlMu() const
Return true if the PPDU is a DL MU PPDU.
Definition he-ppdu.cc:429
void SetHeMuUserInfos(WifiTxVector &txVector, const RuAllocation &ruAllocation, std::optional< Center26ToneRuIndication > center26ToneRuIndication, const HeSigBContentChannels &contentChannels, bool sigBCompression, uint8_t numMuMimoUsers) const
Reconstruct HeMuUserInfoMap from HE-SIG-B header.
Definition he-ppdu.cc:259
virtual bool IsUlMu() const
Return true if the PPDU is an UL MU PPDU.
Definition he-ppdu.cc:435
static uint8_t GetNstsEncodingFromNss(uint8_t nss)
Convert number of spatial streams to NSTS field encoding in HE-SIG-A.
Definition he-ppdu.cc:969
virtual WifiRu::RuSpec GetRuSpec(std::size_t ruAllocIndex, const std::vector< WifiRu::RuSpec > &ruSpecs, RuType ruType, std::size_t ruIndex, MHz_u bw) const
Get the RU specification that has been assigned a given user.
Definition he-ppdu.cc:226
void SetHeSigHeader(const WifiTxVector &txVector)
Fill in the HE-SIG header.
Definition he-ppdu.cc:121
static uint8_t GetNssFromNstsEncoding(uint8_t nsts)
Convert number of spatial streams from NSTS field encoding in HE-SIG-A.
Definition he-ppdu.cc:976
static Time GetGuardIntervalFromEncoding(uint8_t giAndNltfSize)
Convert guard interval from its encoding in HE-SIG-A.
Definition he-ppdu.cc:951
virtual void SetPhyHeaders(const WifiTxVector &txVector, Time ppduDuration)
Fill in the PHY headers.
Definition he-ppdu.cc:94
static HeSigBContentChannels GetHeSigBContentChannels(const WifiTxVector &txVector, uint8_t p20Index)
Get the HE SIG-B content channels for a given PPDU IEEE 802.11ax-2021 27.3.11.8.2 HE-SIG-B content ch...
Definition he-ppdu.cc:665
std::vector< std::vector< HeSigBUserSpecificField > > HeSigBContentChannels
HE SIG-B Content Channels.
Definition he-ppdu.h:50
static uint8_t GetMuMimoUsersEncoding(uint8_t nUsers)
Convert number of MU-MIMO users to its encoding in HE-SIG-A.
Definition he-ppdu.cc:982
static MHz_u GetChannelWidthMhzFromEncoding(uint8_t bandwidth)
Convert channel width expressed in MHz from bandwidth field encoding in HE-SIG-A.
Definition he-ppdu.cc:908
virtual bool IsMu() const
Return true if the PPDU is a MU PPDU.
Definition he-ppdu.cc:423
HePpdu(Ptr< const WifiPsdu > psdu, const WifiTxVector &txVector, const WifiPhyOperatingChannel &channel, Time ppduDuration, uint64_t uid, bool instantiateHeaders=true)
Create an SU HE PPDU, storing a PSDU.
Definition he-ppdu.cc:71
static uint8_t GetChannelWidthEncodingFromMhz(MHz_u channelWidth)
Convert channel width expressed in MHz to bandwidth field encoding in HE-SIG-A.
Definition he-ppdu.cc:887
void SetLSigHeader(Time ppduDuration)
Fill in the L-SIG header.
Definition he-ppdu.cc:102
static uint8_t GetGuardIntervalAndNltfEncoding(Time guardInterval, uint8_t nltf)
Convert guard interval and NLTF to its encoding in HE-SIG-A.
Definition he-ppdu.cc:929
static uint8_t GetMuMimoUsersFromEncoding(uint8_t encoding)
Convert number of MU-MIMO users from its encoding in HE-SIG-A.
Definition he-ppdu.cc:989
RU Specification.
Definition he-ru.h:37
static std::vector< RuSpec > GetRusOfType(MHz_u bw, RuType ruType)
Get the set of distinct RUs of the given type (number of tones) available in a HE PPDU of the given b...
Definition he-ru.cc:511
static std::size_t GetNRus(MHz_u bw, RuType ruType)
Get the number of distinct RUs of the given type (number of tones) available in a HE PPDU of the give...
Definition he-ru.cc:491
uint16_t GetLength() const
Return the LENGTH field of L-SIG (in bytes).
Definition ofdm-ppdu.cc:199
void SetLength(uint16_t length)
Fill the LENGTH field of L-SIG (in bytes).
Definition ofdm-ppdu.cc:192
OFDM PPDU (11a)
Definition ofdm-ppdu.h:36
LSigHeader m_lSig
the L-SIG PHY header
Definition ofdm-ppdu.h:101
Smart pointer class similar to boost::intrusive_ptr.
Simulation virtual time values and global simulation resolution.
Definition nstime.h:94
int64_t GetNanoSeconds() const
Get an approximation of the time stored in this instance in the indicated unit.
Definition nstime.h:407
static WifiMode GetVhtMcs(uint8_t index)
Return the VHT MCS corresponding to the provided index.
Definition vht-phy.cc:334
uint8_t GetMcsValue() const
Definition wifi-mode.cc:151
Class that keeps track of all information about the current PHY operating channel.
bool IsSet() const
Return true if a valid channel has been set, false otherwise.
uint8_t GetPrimaryChannelIndex(MHz_u primaryChannelWidth) const
If the operating channel width is a multiple of 20 MHz, return the index of the primary channel of th...
WifiPhyBand GetPhyBand() const
Return the PHY band of the operating channel.
const WifiPhyOperatingChannel & m_operatingChannel
the operating channel of the PHY
Definition wifi-ppdu.h:201
WifiPreamble m_preamble
the PHY preamble
Definition wifi-ppdu.h:192
WifiConstPsduMap m_psdus
the PSDUs contained in this PPDU
Definition wifi-ppdu.h:194
std::variant< HeRu::RuSpec, EhtRu::RuSpec > RuSpec
variant of the RU specification
Definition wifi-ru.h:27
static MHz_u GetBandwidth(RuType ruType)
Get the approximate bandwidth occupied by a RU.
Definition wifi-ru.cc:78
static std::size_t GetIndex(RuSpec ru)
Get the index of a given RU.
Definition wifi-ru.cc:51
This class mimics the TXVECTOR which is to be passed to the PHY in order to define the parameters whi...
void SetCenter26ToneRuIndication(Center26ToneRuIndication center26ToneRuIndication)
Set CENTER_26_TONE_RU field.
void SetRuAllocation(const RuAllocation &ruAlloc, uint8_t p20Index)
Set RU_ALLOCATION field.
UserInfoMapOrderedByRus GetUserInfoMapOrderedByRus(uint8_t p20Index) const
Get the map of specific user info parameters ordered per increasing frequency RUs.
bool IsSigBCompression() const
Indicate whether the Common field is present in the HE-SIG-B field.
uint8_t GetBssColor() const
Get the BSS color.
const RuAllocation & GetRuAllocation(uint8_t p20Index) const
Get RU_ALLOCATION field.
void SetGuardInterval(Time guardInterval)
Sets the guard interval duration (in nanoseconds)
std::optional< Center26ToneRuIndication > GetCenter26ToneRuIndication() const
Get CENTER_26_TONE_RU field This field is present if format is HE_MU and when channel width is set to...
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.
RuAllocation m_ruAllocation
RU allocations that are going to be carried in SIG-B common field per Table 27-1 IEEE.
void SetHeMuUserInfo(uint16_t staId, HeMuUserInfo userInfo)
Set the HE MU user-specific transmission information for the given STA-ID.
HeMuUserInfo GetHeMuUserInfo(uint16_t staId) const
Get the HE MU user-specific transmission information for the given STA-ID.
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.
uint8_t GetNss(uint16_t staId=SU_STA_ID) const
If this TX vector is associated with an SU PPDU, return the number of spatial streams.
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.
Time GetGuardInterval() const
void SetMode(WifiMode mode)
Sets the selected payload transmission mode.
WifiMode GetSigBMode() const
Get MCS used for SIG-B.
void SetNss(uint8_t nss)
Sets the number of Nss.
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
#define NS_FATAL_ERROR(msg)
Report a fatal error with a message and terminate.
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition log.h:191
#define NS_LOG_FUNCTION(parameters)
If log level LOG_FUNCTION is enabled, this macro will output all input parameters separated by ",...
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition log.h:264
Time MicroSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1369
Time NanoSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1381
Center26ToneRuIndication
Enum for the different values for CENTER_26_TONE_RU.
WifiPpduType
The type of PPDU (SU, DL MU, or UL MU)
@ WIFI_PREAMBLE_HE_TB
@ WIFI_PREAMBLE_HE_MU
@ CENTER_26_TONE_RU_LOW_AND_HIGH_80_MHZ_ALLOCATED
@ CENTER_26_TONE_RU_HIGH_80_MHZ_ALLOCATED
@ CENTER_26_TONE_RU_LOW_80_MHZ_ALLOCATED
@ WIFI_PHY_BAND_2_4GHZ
The 2.4 GHz band.
@ WIFI_PPDU_TYPE_DL_MU
@ WIFI_PPDU_TYPE_UL_MU
@ WIFI_PPDU_TYPE_SU
@ WIFI_MOD_CLASS_HE
HE (Clause 27)
Declaration of ns3::HePhy class and ns3::HeSigAParameters struct.
Declaration of ns3::HePpdu class.
void(* Time)(Time oldValue, Time newValue)
TracedValue callback signature for Time.
Definition nstime.h:865
Every class exported by the ns3 library is enclosed in the ns3 namespace.
std::ostream & operator<<(std::ostream &os, const Angles &a)
Definition angles.cc:148
RuType
The different Resource Unit (RU) types.
Definition wifi-types.h:98
bool IsMu(WifiPreamble preamble)
Return true if a preamble corresponds to a multi-user transmission.
double MHz_u
MHz weak type.
Definition wifi-units.h:31
std::size_t Count20MHzSubchannels(MHz_u channelWidth)
Return the number of 20 MHz subchannels covering the channel width.
Definition wifi-utils.h:136
bool IsDlMu(WifiPreamble preamble)
Return true if a preamble corresponds to a downlink multi-user transmission.
std::unordered_map< uint16_t, Ptr< const WifiPsdu > > WifiConstPsduMap
Map of const PSDUs indexed by STA-ID.
Definition wifi-ppdu.h:38
bool IsUlMu(WifiPreamble preamble)
Return true if a preamble corresponds to a uplink multi-user transmission.
std::vector< uint16_t > RuAllocation
9 bits RU_ALLOCATION per 20 MHz
HE-SIG PHY header for HE MU PPDUs (HE-SIG-A1/A2/B)
Definition he-ppdu.h:79
uint8_t m_bssColor
BSS color field.
Definition he-ppdu.h:81
User Specific Fields in HE-SIG-Bs.
Definition he-ppdu.h:43
uint8_t nss
number of spatial streams
Definition he-ppdu.h:45
HE-SIG PHY header for HE SU PPDUs (HE-SIG-A1/A2)
Definition he-ppdu.h:56
uint8_t m_bssColor
BSS color field.
Definition he-ppdu.h:58
HE-SIG PHY header for HE TB PPDUs (HE-SIG-A1/A2)
Definition he-ppdu.h:69
uint8_t m_bssColor
BSS color field.
Definition he-ppdu.h:71