A Discrete-Event Network Simulator
API
multi-link-element.cc
Go to the documentation of this file.
1/*
2 * Copyright (c) 2021 Universita' degli Studi di Napoli Federico II
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation;
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 *
17 * Author: Stefano Avallone <stavallo@unina.it>
18 */
19
20#include "multi-link-element.h"
21
22#include "ns3/address-utils.h"
23#include "ns3/mgt-headers.h"
24
25#include <utility>
26
27namespace ns3
28{
29
34uint16_t
36{
37 // see Sec. 9.4.2.312.2.1 of 802.11be D1.5
38 return (m_linkIdInfo.has_value() ? 0x0001 : 0x0) |
39 (m_bssParamsChangeCount.has_value() ? 0x0002 : 0x0) |
40 (m_mediumSyncDelayInfo.has_value() ? 0x0004 : 0x0) |
41 (m_emlCapabilities.has_value() ? 0x0008 : 0x0) |
42 (m_mldCapabilities.has_value() ? 0x0010 : 0x0);
43}
44
45uint8_t
47{
48 uint8_t ret = 7; // Common Info Length (1) + MLD MAC Address (6)
49 ret += (m_linkIdInfo.has_value() ? 1 : 0);
50 ret += (m_bssParamsChangeCount.has_value() ? 1 : 0);
51 ret += (m_mediumSyncDelayInfo.has_value() ? 2 : 0);
52 // NOTE Fig. 9-1002h of 802.11be D1.5 reports that the size of the EML Capabilities
53 // subfield is 3 octets, but this is likely a typo (the correct size is 2 octets)
54 ret += (m_emlCapabilities.has_value() ? 2 : 0);
55 ret += (m_mldCapabilities.has_value() ? 2 : 0);
56 return ret;
57}
58
59void
61{
62 start.WriteU8(GetSize()); // Common Info Length
64 if (m_linkIdInfo.has_value())
65 {
66 start.WriteU8(*m_linkIdInfo & 0x0f);
67 }
68 if (m_bssParamsChangeCount.has_value())
69 {
71 }
72 if (m_mediumSyncDelayInfo.has_value())
73 {
74 start.WriteU8(m_mediumSyncDelayInfo->mediumSyncDuration);
75 uint8_t val = m_mediumSyncDelayInfo->mediumSyncOfdmEdThreshold |
76 (m_mediumSyncDelayInfo->mediumSyncMaxNTxops << 4);
77 start.WriteU8(val);
78 }
79 if (m_emlCapabilities.has_value())
80 {
81 uint16_t val =
82 m_emlCapabilities->emlsrSupport | (m_emlCapabilities->emlsrPaddingDelay << 1) |
83 (m_emlCapabilities->emlsrTransitionDelay << 4) |
84 (m_emlCapabilities->emlmrSupport << 7) | (m_emlCapabilities->emlmrDelay << 8) |
85 (m_emlCapabilities->transitionTimeout << 11);
86 start.WriteHtolsbU16(val);
87 }
88 if (m_mldCapabilities.has_value())
89 {
90 uint16_t val =
91 m_mldCapabilities->maxNSimultaneousLinks | (m_mldCapabilities->srsSupport << 4) |
92 (m_mldCapabilities->tidToLinkMappingSupport << 5) |
93 (m_mldCapabilities->freqSepForStrApMld << 7) | (m_mldCapabilities->aarSupport << 12);
94 start.WriteHtolsbU16(val);
95 }
96}
97
98uint8_t
100{
102
103 uint8_t length = i.ReadU8();
105 uint8_t count = 7;
106
107 if ((presence & 0x0001) != 0)
108 {
109 m_linkIdInfo = i.ReadU8() & 0x0f;
110 count++;
111 }
112 if ((presence & 0x0002) != 0)
113 {
115 count++;
116 }
117 if ((presence & 0x0004) != 0)
118 {
120 m_mediumSyncDelayInfo->mediumSyncDuration = i.ReadU8();
121 uint8_t val = i.ReadU8();
122 m_mediumSyncDelayInfo->mediumSyncOfdmEdThreshold = val & 0x0f;
123 m_mediumSyncDelayInfo->mediumSyncMaxNTxops = (val >> 4) & 0x0f;
124 count += 2;
125 }
126 if ((presence & 0x0008) != 0)
127 {
129 uint16_t val = i.ReadLsbtohU16();
130 m_emlCapabilities->emlsrSupport = val & 0x0001;
131 m_emlCapabilities->emlsrPaddingDelay = (val >> 1) & 0x0007;
132 m_emlCapabilities->emlsrTransitionDelay = (val >> 4) & 0x0007;
133 m_emlCapabilities->emlmrSupport = (val >> 7) & 0x0001;
134 m_emlCapabilities->emlmrDelay = (val >> 8) & 0x0007;
135 m_emlCapabilities->transitionTimeout = (val >> 11) & 0x000f;
136 count += 2;
137 }
138 if ((presence & 0x0010) != 0)
139 {
141 uint16_t val = i.ReadLsbtohU16();
142 m_mldCapabilities->maxNSimultaneousLinks = val & 0x000f;
143 m_mldCapabilities->srsSupport = (val >> 4) & 0x0001;
144 m_mldCapabilities->tidToLinkMappingSupport = (val >> 5) & 0x0003;
145 m_mldCapabilities->freqSepForStrApMld = (val >> 7) & 0x001f;
146 m_mldCapabilities->aarSupport = (val >> 12) & 0x0001;
147 count += 2;
148 }
149
150 NS_ABORT_MSG_IF(count != length,
151 "Common Info Length (" << +length
152 << ") differs "
153 "from actual number of bytes read ("
154 << +count << ")");
155 return count;
156}
157
162 : m_frameType(frameType),
163 m_commonInfo(std::in_place_type<std::monostate>) // initialize as UNSET
164{
165}
166
168 : MultiLinkElement(frameType)
169{
170 NS_ASSERT(variant != UNSET);
171 SetVariant(variant);
172}
173
176{
177 return IE_EXTENSION;
178}
179
182{
184}
185
188{
189 return static_cast<Variant>(m_commonInfo.index());
190}
191
192void
194{
195 NS_ABORT_MSG_IF(GetVariant() != UNSET, "Multi-Link Element variant already set");
196 NS_ABORT_MSG_IF(variant == UNSET, "Invalid variant");
197
198 switch (variant)
199 {
200 case BASIC_VARIANT:
202 break;
203 default:
204 NS_ABORT_MSG("Unsupported variant: " << +variant);
205 }
206}
207
208void
210{
211 std::get<BASIC_VARIANT>(m_commonInfo).m_mldMacAddress = address;
212}
213
216{
217 return std::get<BASIC_VARIANT>(m_commonInfo).m_mldMacAddress;
218}
219
220void
222{
223 std::get<BASIC_VARIANT>(m_commonInfo).m_linkIdInfo = (linkIdInfo & 0x0f);
224}
225
226bool
228{
229 return std::get<BASIC_VARIANT>(m_commonInfo).m_linkIdInfo.has_value();
230}
231
232uint8_t
234{
235 return std::get<BASIC_VARIANT>(m_commonInfo).m_linkIdInfo.value();
236}
237
238void
240{
241 std::get<BASIC_VARIANT>(m_commonInfo).m_bssParamsChangeCount = count;
242}
243
244bool
246{
247 return std::get<BASIC_VARIANT>(m_commonInfo).m_bssParamsChangeCount.has_value();
248}
249
250uint8_t
252{
253 return std::get<BASIC_VARIANT>(m_commonInfo).m_bssParamsChangeCount.value();
254}
255
256void
258{
259 int64_t delayUs = delay.GetMicroSeconds();
260 NS_ABORT_MSG_IF(delayUs % 32 != 0, "Delay must be a multiple of 32 microseconds");
261 delayUs /= 32;
262
263 auto& mediumSyncDelayInfo = std::get<BASIC_VARIANT>(m_commonInfo).m_mediumSyncDelayInfo;
264 if (!mediumSyncDelayInfo.has_value())
265 {
266 mediumSyncDelayInfo = CommonInfoBasicMle::MediumSyncDelayInfo{};
267 }
268 mediumSyncDelayInfo.value().mediumSyncDuration = (delayUs & 0xff);
269}
270
271Time
273{
274 return MicroSeconds(
275 (std::get<BASIC_VARIANT>(m_commonInfo).m_mediumSyncDelayInfo.value().mediumSyncDuration) *
276 32);
277}
278
279void
281{
282 NS_ABORT_MSG_IF(threshold < -72 || threshold > -62, "Threshold may range from -72 to -62 dBm");
283 uint8_t value = 72 + threshold;
284
285 auto& mediumSyncDelayInfo = std::get<BASIC_VARIANT>(m_commonInfo).m_mediumSyncDelayInfo;
286 if (!mediumSyncDelayInfo.has_value())
287 {
288 mediumSyncDelayInfo = CommonInfoBasicMle::MediumSyncDelayInfo{};
289 }
290 mediumSyncDelayInfo.value().mediumSyncOfdmEdThreshold = value;
291}
292
293int8_t
295{
296 return (std::get<BASIC_VARIANT>(m_commonInfo)
297 .m_mediumSyncDelayInfo.value()
298 .mediumSyncOfdmEdThreshold) -
299 72;
300}
301
302void
304{
305 NS_ASSERT(nTxops > 0);
306 nTxops--;
307
308 auto& mediumSyncDelayInfo = std::get<BASIC_VARIANT>(m_commonInfo).m_mediumSyncDelayInfo;
309 if (!mediumSyncDelayInfo.has_value())
310 {
311 mediumSyncDelayInfo = CommonInfoBasicMle::MediumSyncDelayInfo{};
312 }
313 mediumSyncDelayInfo.value().mediumSyncMaxNTxops = (nTxops & 0x0f);
314}
315
316uint8_t
318{
319 return (std::get<BASIC_VARIANT>(m_commonInfo)
320 .m_mediumSyncDelayInfo.value()
321 .mediumSyncMaxNTxops) +
322 1;
323}
324
325bool
327{
328 return std::get<BASIC_VARIANT>(m_commonInfo).m_mediumSyncDelayInfo.has_value();
329}
330
332 WifiMacType frameType)
333 : m_variant(variant),
334 m_frameType(frameType),
335 m_staControl(0)
336{
337}
338
340 const PerStaProfileSubelement& perStaProfile)
341 : m_variant(perStaProfile.m_variant),
342 m_frameType(perStaProfile.m_frameType),
343 m_staControl(perStaProfile.m_staControl),
344 m_staMacAddress(perStaProfile.m_staMacAddress)
345{
346 // deep copy of the STA Profile field
347 if (perStaProfile.HasAssocRequest())
348 {
349 m_staProfile = std::make_unique<MgtAssocRequestHeader>(
350 *static_cast<MgtAssocRequestHeader*>(perStaProfile.m_staProfile.get()));
351 }
352 else if (perStaProfile.HasReassocRequest())
353 {
354 m_staProfile = std::make_unique<MgtReassocRequestHeader>(
355 *static_cast<MgtReassocRequestHeader*>(perStaProfile.m_staProfile.get()));
356 }
357 else if (perStaProfile.HasAssocResponse())
358 {
359 m_staProfile = std::make_unique<MgtAssocResponseHeader>(
360 *static_cast<MgtAssocResponseHeader*>(perStaProfile.m_staProfile.get()));
361 }
362}
363
366{
367 // check for self-assignment
368 if (&perStaProfile == this)
369 {
370 return *this;
371 }
372
373 m_variant = perStaProfile.m_variant;
374 m_frameType = perStaProfile.m_frameType;
375 m_staControl = perStaProfile.m_staControl;
376 m_staMacAddress = perStaProfile.m_staMacAddress;
377
378 // deep copy of the STA Profile field
379 if (perStaProfile.HasAssocRequest())
380 {
381 m_staProfile = std::make_unique<MgtAssocRequestHeader>(
382 *static_cast<MgtAssocRequestHeader*>(perStaProfile.m_staProfile.get()));
383 }
384 else if (perStaProfile.HasReassocRequest())
385 {
386 m_staProfile = std::make_unique<MgtReassocRequestHeader>(
387 *static_cast<MgtReassocRequestHeader*>(perStaProfile.m_staProfile.get()));
388 }
389 else if (perStaProfile.HasAssocResponse())
390 {
391 m_staProfile = std::make_unique<MgtAssocResponseHeader>(
392 *static_cast<MgtAssocResponseHeader*>(perStaProfile.m_staProfile.get()));
393 }
394
395 return *this;
396}
397
398void
400{
401 m_staControl &= 0xfff0; // reset Link ID subfield in the STA Control field
402 m_staControl |= (linkId & 0x0f);
403}
404
405uint8_t
407{
408 return static_cast<uint8_t>(m_staControl & 0x000f);
409}
410
411void
413{
414 m_staControl |= 0x0010;
415}
416
417bool
419{
420 return (m_staControl & 0x0010) != 0;
421}
422
423void
425{
426 NS_ABORT_IF(m_variant != BASIC_VARIANT);
427 m_staMacAddress = address;
428 m_staControl |= 0x0020;
429}
430
431bool
433{
434 return (m_staControl & 0x0020) != 0;
435}
436
439{
440 NS_ABORT_IF(!HasStaMacAddress());
441 return m_staMacAddress;
442}
443
444void
446 const std::variant<MgtAssocRequestHeader, MgtReassocRequestHeader>& assoc)
447{
448 switch (m_frameType)
449 {
451 m_staProfile =
452 std::make_unique<MgtAssocRequestHeader>(std::get<MgtAssocRequestHeader>(assoc));
453 break;
455 m_staProfile =
456 std::make_unique<MgtReassocRequestHeader>(std::get<MgtReassocRequestHeader>(assoc));
457 break;
458 default:
459 NS_ABORT_MSG("Invalid frame type: " << m_frameType);
460 }
461}
462
463void
465 std::variant<MgtAssocRequestHeader, MgtReassocRequestHeader>&& assoc)
466{
467 switch (m_frameType)
468 {
470 m_staProfile = std::make_unique<MgtAssocRequestHeader>(
471 std::move(std::get<MgtAssocRequestHeader>(assoc)));
472 break;
474 m_staProfile = std::make_unique<MgtReassocRequestHeader>(
475 std::move(std::get<MgtReassocRequestHeader>(assoc)));
476 break;
477 default:
478 NS_ABORT_MSG("Invalid frame type: " << m_frameType);
479 }
480}
481
482bool
484{
485 return m_staProfile && m_frameType == WIFI_MAC_MGT_ASSOCIATION_REQUEST;
486}
487
488bool
490{
491 return m_staProfile && m_frameType == WIFI_MAC_MGT_REASSOCIATION_REQUEST;
492}
493
496{
497 if (HasAssocRequest())
498 {
499 return *static_cast<MgtAssocRequestHeader*>(m_staProfile.get());
500 }
501 NS_ABORT_UNLESS(HasReassocRequest());
502 return *static_cast<MgtReassocRequestHeader*>(m_staProfile.get());
503}
504
505void
507{
510 m_staProfile = std::make_unique<MgtAssocResponseHeader>(assoc);
511}
512
513void
515{
518 m_staProfile = std::make_unique<MgtAssocResponseHeader>(std::move(assoc));
519}
520
521bool
523{
524 return m_staProfile && (m_frameType == WIFI_MAC_MGT_ASSOCIATION_RESPONSE ||
526}
527
530{
531 NS_ABORT_IF(!HasAssocResponse());
532 return *static_cast<MgtAssocResponseHeader*>(m_staProfile.get());
533}
534
535uint8_t
537{
538 uint8_t ret = 1; // STA Info Length
539
540 if (HasStaMacAddress())
541 {
542 ret += 6;
543 }
544 // TODO add other subfields of the STA Info field
545 return ret;
546}
547
550{
552}
553
554uint16_t
556{
557 uint16_t ret = 2; // STA Control field
558
559 ret += GetStaInfoLength();
560
561 if (HasAssocRequest() || HasReassocRequest() || HasAssocResponse())
562 {
563 ret += m_staProfile->GetSerializedSize();
564 }
565
566 return ret;
567}
568
569void
571{
572 start.WriteHtolsbU16(m_staControl);
573 start.WriteU8(GetStaInfoLength());
574
575 if (HasStaMacAddress())
576 {
577 WriteTo(start, m_staMacAddress);
578 }
579 // TODO add other subfields of the STA Info field
580 if (HasAssocRequest() || HasReassocRequest() || HasAssocResponse())
581 {
582 m_staProfile->Serialize(start);
583 }
584}
585
586uint16_t
588 uint16_t length)
589{
591 uint16_t count = 0;
592
593 m_staControl = i.ReadLsbtohU16();
594 count += 2;
595
596 i.ReadU8(); // STA Info Length
597 count++;
598
599 if (HasStaMacAddress())
600 {
601 ReadFrom(i, m_staMacAddress);
602 count += 6;
603 }
604
605 // TODO add other subfields of the STA Info field
606
607 if (count >= length)
608 {
609 return count;
610 }
611
613 {
615 count += assoc.Deserialize(i);
616 SetAssocRequest(std::move(assoc));
617 }
619 {
621 count += reassoc.Deserialize(i);
622 SetAssocRequest(std::move(reassoc));
623 }
626 {
628 count += assoc.Deserialize(i);
629 SetAssocResponse(assoc);
630 }
631
632 return count;
633}
634
635void
637{
638 auto variant = GetVariant();
639 NS_ABORT_IF(variant == UNSET);
641 m_perStaProfileSubelements.emplace_back(variant, m_frameType);
642}
643
644std::size_t
646{
647 return m_perStaProfileSubelements.size();
648}
649
652{
653 return m_perStaProfileSubelements.at(i);
654}
655
658{
659 return m_perStaProfileSubelements.at(i);
660}
661
662uint16_t
664{
665 uint16_t ret = 3; // ElementIdExt (1) + Multi-Link Control (2)
666
667 // add the Common Info field size (dependent on the Multi-Link Element variant)
668 ret += std::visit(
669 [](auto&& arg) -> uint8_t {
670 using T = std::decay_t<decltype(arg)>;
671 if constexpr (std::is_same_v<T, std::monostate>)
672 {
673 NS_ABORT_MSG("Multi-Link Element variant not set");
674 return 0;
675 }
676 else
677 {
678 return arg.GetSize();
679 }
680 },
682
683 for (const auto& subelement : m_perStaProfileSubelements)
684 {
685 ret += subelement.GetSerializedSize();
686 }
687
688 return ret;
689}
690
691void
693{
694 // serialize the Multi-Link Control and Common Info fields
695 std::visit(
696 [this, &start](auto&& arg) {
697 using T = std::decay_t<decltype(arg)>;
698 if constexpr (std::is_same_v<T, std::monostate>)
699 {
700 NS_ABORT_MSG("Multi-Link Element variant not set");
701 }
702 else
703 {
704 uint16_t mlControl =
705 static_cast<uint8_t>(GetVariant()) + (arg.GetPresenceBitmap() << 4);
706 start.WriteHtolsbU16(mlControl);
707 arg.Serialize(start);
708 }
709 },
711
712 for (const auto& subelement : m_perStaProfileSubelements)
713 {
714 start = subelement.Serialize(start);
715 }
716}
717
718uint16_t
720{
722 uint16_t count = 0;
723
724 uint16_t mlControl = i.ReadLsbtohU16();
725 count += 2;
726
727 SetVariant(static_cast<Variant>(mlControl & 0x0007));
728 uint16_t presence = mlControl >> 4;
729
730 uint8_t nBytes = std::visit(
731 [&i, &presence](auto&& arg) -> uint8_t {
732 using T = std::decay_t<decltype(arg)>;
733 if constexpr (std::is_same_v<T, std::monostate>)
734 {
735 NS_ABORT_MSG("Multi-Link Element variant not set");
736 return 0;
737 }
738 else
739 {
740 return arg.Deserialize(i, presence);
741 }
742 },
744 i.Next(nBytes);
745 count += nBytes;
746
747 while (count < length)
748 {
749 switch (static_cast<SubElementId>(i.PeekU8()))
750 {
754 count = i.GetDistanceFrom(start);
755 break;
756 default:
757 NS_ABORT_MSG("Unsupported Subelement ID: " << +i.PeekU8());
758 }
759 }
760
761 return count;
762}
763
764} // namespace ns3
iterator in a Buffer instance
Definition: buffer.h:100
uint8_t ReadU8()
Definition: buffer.h:1027
uint16_t ReadLsbtohU16()
Definition: buffer.cc:1067
uint8_t PeekU8()
Definition: buffer.h:1006
uint32_t GetDistanceFrom(const Iterator &o) const
Definition: buffer.cc:783
void Next()
go forward by one byte
Definition: buffer.h:853
an EUI-48 address
Definition: mac48-address.h:46
Implement the header for management frames of type association request.
Definition: mgt-headers.h:54
uint32_t Deserialize(Buffer::Iterator start) override
Implement the header for management frames of type association and reassociation response.
Definition: mgt-headers.h:446
uint32_t Deserialize(Buffer::Iterator start) override
Implement the header for management frames of type reassociation request.
Definition: mgt-headers.h:247
uint32_t Deserialize(Buffer::Iterator start) override
Simulation virtual time values and global simulation resolution.
Definition: nstime.h:105
int64_t GetMicroSeconds() const
Get an approximation of the time stored in this instance in the indicated unit.
Definition: nstime.h:412
Buffer::Iterator Deserialize(Buffer::Iterator i)
Deserialize entire IE (which may possibly be fragmented into multiple elements), which must be presen...
#define NS_ASSERT(condition)
At runtime, in debugging builds, if this condition is not true, the program prints the source file,...
Definition: assert.h:66
#define NS_ABORT_MSG(msg)
Unconditional abnormal program termination with a message.
Definition: abort.h:49
#define NS_ABORT_UNLESS(cond)
Abnormal program termination if a condition is false.
Definition: abort.h:129
#define NS_ABORT_MSG_IF(cond, msg)
Abnormal program termination if a condition is true, with a message.
Definition: abort.h:108
#define NS_ABORT_IF(cond)
Abnormal program termination if a condition is true.
Definition: abort.h:76
Time MicroSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition: nstime.h:1362
Variant
Multi-Link element variants.
address
Definition: first.py:40
Every class exported by the ns3 library is enclosed in the ns3 namespace.
std::variant< std::reference_wrapper< MgtAssocRequestHeader >, std::reference_wrapper< MgtReassocRequestHeader > > AssocReqRefVariant
variant holding a reference to a (Re)Association Request
Definition: ap-wifi-mac.h:53
WifiMacType
Combination of valid MAC header type/subtype.
@ WIFI_MAC_MGT_ASSOCIATION_RESPONSE
@ WIFI_MAC_MGT_ASSOCIATION_REQUEST
@ WIFI_MAC_MGT_REASSOCIATION_REQUEST
@ WIFI_MAC_DATA
@ WIFI_MAC_MGT_REASSOCIATION_RESPONSE
void WriteTo(Buffer::Iterator &i, Ipv4Address ad)
Write an Ipv4Address to a Buffer.
uint8_t WifiInformationElementId
This type is used to represent an Information Element ID.
void ReadFrom(Buffer::Iterator &i, Ipv4Address &ad)
Read an Ipv4Address from a Buffer.
value
Definition: second.py:41
STL namespace.
def start()
Definition: core.py:1861
Medium Synchronization Delay Information subfield.
uint8_t mediumSyncOfdmEdThreshold
Medium Synchronization OFDM ED Threshold.
uint8_t mediumSyncDuration
Medium Synchronization Duration.
uint8_t mediumSyncMaxNTxops
Medium Synchronization MAximum Number of TXOPs.
Common Info field of the Basic Multi-Link element.
uint16_t GetPresenceBitmap() const
Get the Presence Bitmap subfield of the Common Info field.
uint8_t GetSize() const
Get the size of the serialized Common Info field.
std::optional< EmlCapabilities > m_emlCapabilities
EML Capabilities.
uint8_t Deserialize(Buffer::Iterator start, uint16_t presence)
Deserialize the Common Info field.
std::optional< MldCapabilities > m_mldCapabilities
MLD Capabilities.
void Serialize(Buffer::Iterator &start) const
Serialize the Common Info field.
std::optional< MediumSyncDelayInfo > m_mediumSyncDelayInfo
Medium Synchronization Delay Information.
Mac48Address m_mldMacAddress
Subfields.
std::optional< uint8_t > m_bssParamsChangeCount
BSS Parameters Change Count.
std::optional< uint8_t > m_linkIdInfo
Link ID Info.
#define IE_EXTENSION
#define IE_EXT_MULTI_LINK_ELEMENT