Files
unison/src/wifi/model/eht/multi-link-element.cc
2022-10-14 14:13:12 +00:00

765 lines
22 KiB
C++

/*
* Copyright (c) 2021 Universita' degli Studi di Napoli Federico II
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Author: Stefano Avallone <stavallo@unina.it>
*/
#include "multi-link-element.h"
#include "ns3/address-utils.h"
#include "ns3/mgt-headers.h"
#include <utility>
namespace ns3
{
/**
* CommonInfoBasicMle
*/
uint16_t
CommonInfoBasicMle::GetPresenceBitmap() const
{
// see Sec. 9.4.2.312.2.1 of 802.11be D1.5
return (m_linkIdInfo.has_value() ? 0x0001 : 0x0) |
(m_bssParamsChangeCount.has_value() ? 0x0002 : 0x0) |
(m_mediumSyncDelayInfo.has_value() ? 0x0004 : 0x0) |
(m_emlCapabilities.has_value() ? 0x0008 : 0x0) |
(m_mldCapabilities.has_value() ? 0x0010 : 0x0);
}
uint8_t
CommonInfoBasicMle::GetSize() const
{
uint8_t ret = 7; // Common Info Length (1) + MLD MAC Address (6)
ret += (m_linkIdInfo.has_value() ? 1 : 0);
ret += (m_bssParamsChangeCount.has_value() ? 1 : 0);
ret += (m_mediumSyncDelayInfo.has_value() ? 2 : 0);
// NOTE Fig. 9-1002h of 802.11be D1.5 reports that the size of the EML Capabilities
// subfield is 3 octets, but this is likely a typo (the correct size is 2 octets)
ret += (m_emlCapabilities.has_value() ? 2 : 0);
ret += (m_mldCapabilities.has_value() ? 2 : 0);
return ret;
}
void
CommonInfoBasicMle::Serialize(Buffer::Iterator& start) const
{
start.WriteU8(GetSize()); // Common Info Length
WriteTo(start, m_mldMacAddress);
if (m_linkIdInfo.has_value())
{
start.WriteU8(*m_linkIdInfo & 0x0f);
}
if (m_bssParamsChangeCount.has_value())
{
start.WriteU8(*m_bssParamsChangeCount);
}
if (m_mediumSyncDelayInfo.has_value())
{
start.WriteU8(m_mediumSyncDelayInfo->mediumSyncDuration);
uint8_t val = m_mediumSyncDelayInfo->mediumSyncOfdmEdThreshold |
(m_mediumSyncDelayInfo->mediumSyncMaxNTxops << 4);
start.WriteU8(val);
}
if (m_emlCapabilities.has_value())
{
uint16_t val =
m_emlCapabilities->emlsrSupport | (m_emlCapabilities->emlsrPaddingDelay << 1) |
(m_emlCapabilities->emlsrTransitionDelay << 4) |
(m_emlCapabilities->emlmrSupport << 7) | (m_emlCapabilities->emlmrDelay << 8) |
(m_emlCapabilities->transitionTimeout << 11);
start.WriteHtolsbU16(val);
}
if (m_mldCapabilities.has_value())
{
uint16_t val =
m_mldCapabilities->maxNSimultaneousLinks | (m_mldCapabilities->srsSupport << 4) |
(m_mldCapabilities->tidToLinkMappingSupport << 5) |
(m_mldCapabilities->freqSepForStrApMld << 7) | (m_mldCapabilities->aarSupport << 12);
start.WriteHtolsbU16(val);
}
}
uint8_t
CommonInfoBasicMle::Deserialize(Buffer::Iterator start, uint16_t presence)
{
Buffer::Iterator i = start;
uint8_t length = i.ReadU8();
ReadFrom(i, m_mldMacAddress);
uint8_t count = 7;
if ((presence & 0x0001) != 0)
{
m_linkIdInfo = i.ReadU8() & 0x0f;
count++;
}
if ((presence & 0x0002) != 0)
{
m_bssParamsChangeCount = i.ReadU8();
count++;
}
if ((presence & 0x0004) != 0)
{
m_mediumSyncDelayInfo = MediumSyncDelayInfo();
m_mediumSyncDelayInfo->mediumSyncDuration = i.ReadU8();
uint8_t val = i.ReadU8();
m_mediumSyncDelayInfo->mediumSyncOfdmEdThreshold = val & 0x0f;
m_mediumSyncDelayInfo->mediumSyncMaxNTxops = (val >> 4) & 0x0f;
count += 2;
}
if ((presence & 0x0008) != 0)
{
m_emlCapabilities = EmlCapabilities();
uint16_t val = i.ReadLsbtohU16();
m_emlCapabilities->emlsrSupport = val & 0x0001;
m_emlCapabilities->emlsrPaddingDelay = (val >> 1) & 0x0007;
m_emlCapabilities->emlsrTransitionDelay = (val >> 4) & 0x0007;
m_emlCapabilities->emlmrSupport = (val >> 7) & 0x0001;
m_emlCapabilities->emlmrDelay = (val >> 8) & 0x0007;
m_emlCapabilities->transitionTimeout = (val >> 11) & 0x000f;
count += 2;
}
if ((presence & 0x0010) != 0)
{
m_mldCapabilities = MldCapabilities();
uint16_t val = i.ReadLsbtohU16();
m_mldCapabilities->maxNSimultaneousLinks = val & 0x000f;
m_mldCapabilities->srsSupport = (val >> 4) & 0x0001;
m_mldCapabilities->tidToLinkMappingSupport = (val >> 5) & 0x0003;
m_mldCapabilities->freqSepForStrApMld = (val >> 7) & 0x001f;
m_mldCapabilities->aarSupport = (val >> 12) & 0x0001;
count += 2;
}
NS_ABORT_MSG_IF(count != length,
"Common Info Length (" << +length
<< ") differs "
"from actual number of bytes read ("
<< +count << ")");
return count;
}
/**
* MultiLinkElement
*/
MultiLinkElement::MultiLinkElement(WifiMacType frameType)
: m_frameType(frameType),
m_commonInfo(std::in_place_type<std::monostate>) // initialize as UNSET
{
}
MultiLinkElement::MultiLinkElement(Variant variant, WifiMacType frameType)
: MultiLinkElement(frameType)
{
NS_ASSERT(variant != UNSET);
SetVariant(variant);
}
WifiInformationElementId
MultiLinkElement::ElementId() const
{
return IE_EXTENSION;
}
WifiInformationElementId
MultiLinkElement::ElementIdExt() const
{
return IE_EXT_MULTI_LINK_ELEMENT;
}
MultiLinkElement::Variant
MultiLinkElement::GetVariant() const
{
return static_cast<Variant>(m_commonInfo.index());
}
void
MultiLinkElement::SetVariant(Variant variant)
{
NS_ABORT_MSG_IF(GetVariant() != UNSET, "Multi-Link Element variant already set");
NS_ABORT_MSG_IF(variant == UNSET, "Invalid variant");
switch (variant)
{
case BASIC_VARIANT:
m_commonInfo = CommonInfoBasicMle();
break;
default:
NS_ABORT_MSG("Unsupported variant: " << +variant);
}
}
void
MultiLinkElement::SetMldMacAddress(Mac48Address address)
{
std::get<BASIC_VARIANT>(m_commonInfo).m_mldMacAddress = address;
}
Mac48Address
MultiLinkElement::GetMldMacAddress() const
{
return std::get<BASIC_VARIANT>(m_commonInfo).m_mldMacAddress;
}
void
MultiLinkElement::SetLinkIdInfo(uint8_t linkIdInfo)
{
std::get<BASIC_VARIANT>(m_commonInfo).m_linkIdInfo = (linkIdInfo & 0x0f);
}
bool
MultiLinkElement::HasLinkIdInfo() const
{
return std::get<BASIC_VARIANT>(m_commonInfo).m_linkIdInfo.has_value();
}
uint8_t
MultiLinkElement::GetLinkIdInfo() const
{
return std::get<BASIC_VARIANT>(m_commonInfo).m_linkIdInfo.value();
}
void
MultiLinkElement::SetBssParamsChangeCount(uint8_t count)
{
std::get<BASIC_VARIANT>(m_commonInfo).m_bssParamsChangeCount = count;
}
bool
MultiLinkElement::HasBssParamsChangeCount() const
{
return std::get<BASIC_VARIANT>(m_commonInfo).m_bssParamsChangeCount.has_value();
}
uint8_t
MultiLinkElement::GetBssParamsChangeCount() const
{
return std::get<BASIC_VARIANT>(m_commonInfo).m_bssParamsChangeCount.value();
}
void
MultiLinkElement::SetMediumSyncDelayTimer(Time delay)
{
int64_t delayUs = delay.GetMicroSeconds();
NS_ABORT_MSG_IF(delayUs % 32 != 0, "Delay must be a multiple of 32 microseconds");
delayUs /= 32;
auto& mediumSyncDelayInfo = std::get<BASIC_VARIANT>(m_commonInfo).m_mediumSyncDelayInfo;
if (!mediumSyncDelayInfo.has_value())
{
mediumSyncDelayInfo = CommonInfoBasicMle::MediumSyncDelayInfo{};
}
mediumSyncDelayInfo.value().mediumSyncDuration = (delayUs & 0xff);
}
Time
MultiLinkElement::GetMediumSyncDelayTimer() const
{
return MicroSeconds(
(std::get<BASIC_VARIANT>(m_commonInfo).m_mediumSyncDelayInfo.value().mediumSyncDuration) *
32);
}
void
MultiLinkElement::SetMediumSyncOfdmEdThreshold(int8_t threshold)
{
NS_ABORT_MSG_IF(threshold < -72 || threshold > -62, "Threshold may range from -72 to -62 dBm");
uint8_t value = 72 + threshold;
auto& mediumSyncDelayInfo = std::get<BASIC_VARIANT>(m_commonInfo).m_mediumSyncDelayInfo;
if (!mediumSyncDelayInfo.has_value())
{
mediumSyncDelayInfo = CommonInfoBasicMle::MediumSyncDelayInfo{};
}
mediumSyncDelayInfo.value().mediumSyncOfdmEdThreshold = value;
}
int8_t
MultiLinkElement::GetMediumSyncOfdmEdThreshold() const
{
return (std::get<BASIC_VARIANT>(m_commonInfo)
.m_mediumSyncDelayInfo.value()
.mediumSyncOfdmEdThreshold) -
72;
}
void
MultiLinkElement::SetMediumSyncMaxNTxops(uint8_t nTxops)
{
NS_ASSERT(nTxops > 0);
nTxops--;
auto& mediumSyncDelayInfo = std::get<BASIC_VARIANT>(m_commonInfo).m_mediumSyncDelayInfo;
if (!mediumSyncDelayInfo.has_value())
{
mediumSyncDelayInfo = CommonInfoBasicMle::MediumSyncDelayInfo{};
}
mediumSyncDelayInfo.value().mediumSyncMaxNTxops = (nTxops & 0x0f);
}
uint8_t
MultiLinkElement::GetMediumSyncMaxNTxops() const
{
return (std::get<BASIC_VARIANT>(m_commonInfo)
.m_mediumSyncDelayInfo.value()
.mediumSyncMaxNTxops) +
1;
}
bool
MultiLinkElement::HasMediumSyncDelayInfo() const
{
return std::get<BASIC_VARIANT>(m_commonInfo).m_mediumSyncDelayInfo.has_value();
}
MultiLinkElement::PerStaProfileSubelement::PerStaProfileSubelement(Variant variant,
WifiMacType frameType)
: m_variant(variant),
m_frameType(frameType),
m_staControl(0)
{
}
MultiLinkElement::PerStaProfileSubelement::PerStaProfileSubelement(
const PerStaProfileSubelement& perStaProfile)
: m_variant(perStaProfile.m_variant),
m_frameType(perStaProfile.m_frameType),
m_staControl(perStaProfile.m_staControl),
m_staMacAddress(perStaProfile.m_staMacAddress)
{
// deep copy of the STA Profile field
if (perStaProfile.HasAssocRequest())
{
m_staProfile = std::make_unique<MgtAssocRequestHeader>(
*static_cast<MgtAssocRequestHeader*>(perStaProfile.m_staProfile.get()));
}
else if (perStaProfile.HasReassocRequest())
{
m_staProfile = std::make_unique<MgtReassocRequestHeader>(
*static_cast<MgtReassocRequestHeader*>(perStaProfile.m_staProfile.get()));
}
else if (perStaProfile.HasAssocResponse())
{
m_staProfile = std::make_unique<MgtAssocResponseHeader>(
*static_cast<MgtAssocResponseHeader*>(perStaProfile.m_staProfile.get()));
}
}
MultiLinkElement::PerStaProfileSubelement&
MultiLinkElement::PerStaProfileSubelement::operator=(const PerStaProfileSubelement& perStaProfile)
{
// check for self-assignment
if (&perStaProfile == this)
{
return *this;
}
m_variant = perStaProfile.m_variant;
m_frameType = perStaProfile.m_frameType;
m_staControl = perStaProfile.m_staControl;
m_staMacAddress = perStaProfile.m_staMacAddress;
// deep copy of the STA Profile field
if (perStaProfile.HasAssocRequest())
{
m_staProfile = std::make_unique<MgtAssocRequestHeader>(
*static_cast<MgtAssocRequestHeader*>(perStaProfile.m_staProfile.get()));
}
else if (perStaProfile.HasReassocRequest())
{
m_staProfile = std::make_unique<MgtReassocRequestHeader>(
*static_cast<MgtReassocRequestHeader*>(perStaProfile.m_staProfile.get()));
}
else if (perStaProfile.HasAssocResponse())
{
m_staProfile = std::make_unique<MgtAssocResponseHeader>(
*static_cast<MgtAssocResponseHeader*>(perStaProfile.m_staProfile.get()));
}
return *this;
}
void
MultiLinkElement::PerStaProfileSubelement::SetLinkId(uint8_t linkId)
{
m_staControl &= 0xfff0; // reset Link ID subfield in the STA Control field
m_staControl |= (linkId & 0x0f);
}
uint8_t
MultiLinkElement::PerStaProfileSubelement::GetLinkId() const
{
return static_cast<uint8_t>(m_staControl & 0x000f);
}
void
MultiLinkElement::PerStaProfileSubelement::SetCompleteProfile()
{
m_staControl |= 0x0010;
}
bool
MultiLinkElement::PerStaProfileSubelement::IsCompleteProfileSet() const
{
return (m_staControl & 0x0010) != 0;
}
void
MultiLinkElement::PerStaProfileSubelement::SetStaMacAddress(Mac48Address address)
{
NS_ABORT_IF(m_variant != BASIC_VARIANT);
m_staMacAddress = address;
m_staControl |= 0x0020;
}
bool
MultiLinkElement::PerStaProfileSubelement::HasStaMacAddress() const
{
return (m_staControl & 0x0020) != 0;
}
Mac48Address
MultiLinkElement::PerStaProfileSubelement::GetStaMacAddress() const
{
NS_ABORT_IF(!HasStaMacAddress());
return m_staMacAddress;
}
void
MultiLinkElement::PerStaProfileSubelement::SetAssocRequest(
const std::variant<MgtAssocRequestHeader, MgtReassocRequestHeader>& assoc)
{
switch (m_frameType)
{
case WIFI_MAC_MGT_ASSOCIATION_REQUEST:
m_staProfile =
std::make_unique<MgtAssocRequestHeader>(std::get<MgtAssocRequestHeader>(assoc));
break;
case WIFI_MAC_MGT_REASSOCIATION_REQUEST:
m_staProfile =
std::make_unique<MgtReassocRequestHeader>(std::get<MgtReassocRequestHeader>(assoc));
break;
default:
NS_ABORT_MSG("Invalid frame type: " << m_frameType);
}
}
void
MultiLinkElement::PerStaProfileSubelement::SetAssocRequest(
std::variant<MgtAssocRequestHeader, MgtReassocRequestHeader>&& assoc)
{
switch (m_frameType)
{
case WIFI_MAC_MGT_ASSOCIATION_REQUEST:
m_staProfile = std::make_unique<MgtAssocRequestHeader>(
std::move(std::get<MgtAssocRequestHeader>(assoc)));
break;
case WIFI_MAC_MGT_REASSOCIATION_REQUEST:
m_staProfile = std::make_unique<MgtReassocRequestHeader>(
std::move(std::get<MgtReassocRequestHeader>(assoc)));
break;
default:
NS_ABORT_MSG("Invalid frame type: " << m_frameType);
}
}
bool
MultiLinkElement::PerStaProfileSubelement::HasAssocRequest() const
{
return m_staProfile && m_frameType == WIFI_MAC_MGT_ASSOCIATION_REQUEST;
}
bool
MultiLinkElement::PerStaProfileSubelement::HasReassocRequest() const
{
return m_staProfile && m_frameType == WIFI_MAC_MGT_REASSOCIATION_REQUEST;
}
AssocReqRefVariant
MultiLinkElement::PerStaProfileSubelement::GetAssocRequest() const
{
if (HasAssocRequest())
{
return *static_cast<MgtAssocRequestHeader*>(m_staProfile.get());
}
NS_ABORT_UNLESS(HasReassocRequest());
return *static_cast<MgtReassocRequestHeader*>(m_staProfile.get());
}
void
MultiLinkElement::PerStaProfileSubelement::SetAssocResponse(const MgtAssocResponseHeader& assoc)
{
NS_ABORT_IF(m_frameType != WIFI_MAC_MGT_ASSOCIATION_RESPONSE &&
m_frameType != WIFI_MAC_MGT_REASSOCIATION_RESPONSE);
m_staProfile = std::make_unique<MgtAssocResponseHeader>(assoc);
}
void
MultiLinkElement::PerStaProfileSubelement::SetAssocResponse(MgtAssocResponseHeader&& assoc)
{
NS_ABORT_IF(m_frameType != WIFI_MAC_MGT_ASSOCIATION_RESPONSE &&
m_frameType != WIFI_MAC_MGT_REASSOCIATION_RESPONSE);
m_staProfile = std::make_unique<MgtAssocResponseHeader>(std::move(assoc));
}
bool
MultiLinkElement::PerStaProfileSubelement::HasAssocResponse() const
{
return m_staProfile && (m_frameType == WIFI_MAC_MGT_ASSOCIATION_RESPONSE ||
m_frameType == WIFI_MAC_MGT_REASSOCIATION_RESPONSE);
}
MgtAssocResponseHeader&
MultiLinkElement::PerStaProfileSubelement::GetAssocResponse() const
{
NS_ABORT_IF(!HasAssocResponse());
return *static_cast<MgtAssocResponseHeader*>(m_staProfile.get());
}
uint8_t
MultiLinkElement::PerStaProfileSubelement::GetStaInfoLength() const
{
uint8_t ret = 1; // STA Info Length
if (HasStaMacAddress())
{
ret += 6;
}
// TODO add other subfields of the STA Info field
return ret;
}
WifiInformationElementId
MultiLinkElement::PerStaProfileSubelement::ElementId() const
{
return PER_STA_PROFILE_SUBELEMENT_ID;
}
uint16_t
MultiLinkElement::PerStaProfileSubelement::GetInformationFieldSize() const
{
uint16_t ret = 2; // STA Control field
ret += GetStaInfoLength();
if (HasAssocRequest() || HasReassocRequest() || HasAssocResponse())
{
ret += m_staProfile->GetSerializedSize();
}
return ret;
}
void
MultiLinkElement::PerStaProfileSubelement::SerializeInformationField(Buffer::Iterator start) const
{
start.WriteHtolsbU16(m_staControl);
start.WriteU8(GetStaInfoLength());
if (HasStaMacAddress())
{
WriteTo(start, m_staMacAddress);
}
// TODO add other subfields of the STA Info field
if (HasAssocRequest() || HasReassocRequest() || HasAssocResponse())
{
m_staProfile->Serialize(start);
}
}
uint16_t
MultiLinkElement::PerStaProfileSubelement::DeserializeInformationField(Buffer::Iterator start,
uint16_t length)
{
Buffer::Iterator i = start;
uint16_t count = 0;
m_staControl = i.ReadLsbtohU16();
count += 2;
i.ReadU8(); // STA Info Length
count++;
if (HasStaMacAddress())
{
ReadFrom(i, m_staMacAddress);
count += 6;
}
// TODO add other subfields of the STA Info field
if (count >= length)
{
return count;
}
if (m_frameType == WIFI_MAC_MGT_ASSOCIATION_REQUEST)
{
MgtAssocRequestHeader assoc;
count += assoc.Deserialize(i);
SetAssocRequest(std::move(assoc));
}
else if (m_frameType == WIFI_MAC_MGT_REASSOCIATION_REQUEST)
{
MgtReassocRequestHeader reassoc;
count += reassoc.Deserialize(i);
SetAssocRequest(std::move(reassoc));
}
else if (m_frameType == WIFI_MAC_MGT_ASSOCIATION_RESPONSE ||
m_frameType == WIFI_MAC_MGT_REASSOCIATION_RESPONSE)
{
MgtAssocResponseHeader assoc;
count += assoc.Deserialize(i);
SetAssocResponse(assoc);
}
return count;
}
void
MultiLinkElement::AddPerStaProfileSubelement()
{
auto variant = GetVariant();
NS_ABORT_IF(variant == UNSET);
NS_ABORT_IF(m_frameType == WIFI_MAC_DATA);
m_perStaProfileSubelements.emplace_back(variant, m_frameType);
}
std::size_t
MultiLinkElement::GetNPerStaProfileSubelements() const
{
return m_perStaProfileSubelements.size();
}
MultiLinkElement::PerStaProfileSubelement&
MultiLinkElement::GetPerStaProfile(std::size_t i)
{
return m_perStaProfileSubelements.at(i);
}
const MultiLinkElement::PerStaProfileSubelement&
MultiLinkElement::GetPerStaProfile(std::size_t i) const
{
return m_perStaProfileSubelements.at(i);
}
uint16_t
MultiLinkElement::GetInformationFieldSize() const
{
uint16_t ret = 3; // ElementIdExt (1) + Multi-Link Control (2)
// add the Common Info field size (dependent on the Multi-Link Element variant)
ret += std::visit(
[](auto&& arg) -> uint8_t {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::monostate>)
{
NS_ABORT_MSG("Multi-Link Element variant not set");
return 0;
}
else
{
return arg.GetSize();
}
},
m_commonInfo);
for (const auto& subelement : m_perStaProfileSubelements)
{
ret += subelement.GetSerializedSize();
}
return ret;
}
void
MultiLinkElement::SerializeInformationField(Buffer::Iterator start) const
{
// serialize the Multi-Link Control and Common Info fields
std::visit(
[this, &start](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::monostate>)
{
NS_ABORT_MSG("Multi-Link Element variant not set");
}
else
{
uint16_t mlControl =
static_cast<uint8_t>(GetVariant()) + (arg.GetPresenceBitmap() << 4);
start.WriteHtolsbU16(mlControl);
arg.Serialize(start);
}
},
m_commonInfo);
for (const auto& subelement : m_perStaProfileSubelements)
{
start = subelement.Serialize(start);
}
}
uint16_t
MultiLinkElement::DeserializeInformationField(Buffer::Iterator start, uint16_t length)
{
Buffer::Iterator i = start;
uint16_t count = 0;
uint16_t mlControl = i.ReadLsbtohU16();
count += 2;
SetVariant(static_cast<Variant>(mlControl & 0x0007));
uint16_t presence = mlControl >> 4;
uint8_t nBytes = std::visit(
[&i, &presence](auto&& arg) -> uint8_t {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::monostate>)
{
NS_ABORT_MSG("Multi-Link Element variant not set");
return 0;
}
else
{
return arg.Deserialize(i, presence);
}
},
m_commonInfo);
i.Next(nBytes);
count += nBytes;
while (count < length)
{
switch (static_cast<SubElementId>(i.PeekU8()))
{
case PER_STA_PROFILE_SUBELEMENT_ID:
AddPerStaProfileSubelement();
i = GetPerStaProfile(GetNPerStaProfileSubelements() - 1).Deserialize(i);
count = i.GetDistanceFrom(start);
break;
default:
NS_ABORT_MSG("Unsupported Subelement ID: " << +i.PeekU8());
}
}
return count;
}
} // namespace ns3