[border-agent] add vendor name and model to advertised TXT data (#12578)

This commit updates `BorderAgent` to parse the provided extra vendor
TXT data (`otBorderAgentSetVendorTxtData()`) to check if the vendor
name (`vn`) and model (`mn`) keys are already present. If they are
absent, it would use and encode the values from `VendorInfo` into the
advertised TXT data. This harmonizes the use of `VendorInfo` values
across various modules, network diagnostics, and the `BorderAgent`
TXT data.

Note that this change only impacts the system when
`OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE` is enabled,
meaning the Border Agent itself directly manages the registration and
updating of the mDNS MeshCoP service.

The generated TXT data from `otBorderAgentGetMeshCoPServiceTxtData()`
remains unchanged. This API is intended for use cases where the
next-layer code manages the publishing of the mDNS MeshCoP service,
and as documented, the TXT data from this API does not include any
vendor-specific information.

This commit updates `test_border_agent` to validate the newly
added behavior.
This commit is contained in:
Abtin Keshavarzian
2026-03-02 12:04:08 -06:00
committed by GitHub
parent 3289054650
commit f23519e531
8 changed files with 261 additions and 60 deletions
+7
View File
@@ -82,6 +82,13 @@ void Data::TakeFrom(Data &&aData)
}
}
void Data::TakeFrom(uint8_t *&aHeapAllocatedBuffer, uint16_t aLength)
{
Free();
mData.Init(aHeapAllocatedBuffer, aLength);
aHeapAllocatedBuffer = nullptr;
}
bool Data::Matches(const uint8_t *aBuffer, uint16_t aLength) const
{
bool matches = false;
+13
View File
@@ -140,6 +140,19 @@ public:
*/
void TakeFrom(Data &&aData);
/**
* Sets the `Heap::Data` by taking ownership of a given heap-allocated buffer.
*
* After this call, the `Heap::Data` will take ownership of the buffer and free it when done. The
* @p aHeapAllocatedBuffer pointer is set to `nullptr` to ensure the caller does not retain a pointer to the
* transferred buffer.
*
* @param[in,out] aHeapAllocatedBuffer A reference to a pointer to a heap-allocated buffer.
* On exit, it is set to `nullptr`.
* @param[in] aLength The length of the buffer (number of bytes).
*/
void TakeFrom(uint8_t *&aHeapAllocatedBuffer, uint16_t aLength);
/**
* Casts the `Heap::Data` to an rvalue reference.
*
+4 -25
View File
@@ -432,38 +432,19 @@ void Manager::ConstrcutServiceName(const char *aBaseName, Dns::Name::LabelBuffer
void Manager::RegisterService(void)
{
Dnssd::Service service;
uint16_t vendorDataLength;
uint8_t *txtDataBuffer;
uint16_t txtDataBufferSize;
uint16_t txtDataLength;
Heap::Data txtData;
VerifyOrExit(IsEnabled());
VerifyOrExit(Get<Dnssd>().IsReady());
// Allocate a large enough buffer to fit both the TXT data
// generated by Border Agent itself and the vendor extra
// TXT data. The vendor TXT Data is appended at the
// end.
vendorDataLength = Get<TxtData>().GetVendorData().GetLength();
txtDataBufferSize = kTxtDataMaxSize + vendorDataLength;
txtDataBuffer = reinterpret_cast<uint8_t *>(Heap::CAlloc(txtDataBufferSize, sizeof(uint8_t)));
OT_ASSERT(txtDataBuffer != nullptr);
SuccessOrAssert(Get<TxtData>().Prepare(txtDataBuffer, txtDataBufferSize, txtDataLength));
if (vendorDataLength != 0)
{
Get<TxtData>().GetVendorData().CopyBytesTo(txtDataBuffer + txtDataLength);
txtDataLength += vendorDataLength;
}
Get<TxtData>().PrepareWithVendorData(txtData);
service.Clear();
service.mServiceInstance = GetServiceName();
service.mServiceType = kServiceType;
service.mPort = IsRunning() ? GetUdpPort() : kDummyUdpPort;
service.mTxtData = txtDataBuffer;
service.mTxtDataLength = txtDataLength;
service.mTxtData = txtData.GetBytes();
service.mTxtDataLength = txtData.GetLength();
#if OPENTHREAD_CONFIG_BORDER_AGENT_ADMITTER_ENABLE
if (Get<Admitter>().IsPrimeAdmitter())
@@ -475,8 +456,6 @@ void Manager::RegisterService(void)
Get<Dnssd>().RegisterService(service, /* aRequestId */ 0, /* aCallback */ nullptr);
Heap::Free(txtDataBuffer);
exit:
return;
}
-1
View File
@@ -264,7 +264,6 @@ public:
private:
static constexpr uint16_t kUdpPort = OPENTHREAD_CONFIG_BORDER_AGENT_UDP_PORT;
static constexpr uint32_t kKeepAliveTimeout = 50 * 1000; // Timeout to reject a commissioner (in msec)
static constexpr uint16_t kTxtDataMaxSize = OT_BORDER_AGENT_MESHCOP_SERVICE_TXT_DATA_MAX_LENGTH;
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
static constexpr uint16_t kDummyUdpPort = 49152;
+104 -7
View File
@@ -57,22 +57,28 @@ const char TxtData::Key::kBbrSeqNum[] = "sq";
const char TxtData::Key::kBbrPort[] = "bb";
const char TxtData::Key::kOmrPrefix[] = "omr";
const char TxtData::Key::kExtAddress[] = "xa";
#if OPENTHREAD_CONFIG_BORDER_AGENT_TXT_DATA_PARSER_ENABLE
const char TxtData::Key::kVendorName[] = "vn";
const char TxtData::Key::kModelName[] = "mn";
#endif
const char TxtData::Key::kVendorName[] = "vn";
const char TxtData::Key::kModelName[] = "mn";
TxtData::TxtData(Instance &aInstance)
: InstanceLocator(aInstance)
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
, mChangedTask(aInstance)
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
, mShouldAddVendorName(true)
, mShouldAddVendorModel(true)
#endif
{
}
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
Error TxtData::Prepare(uint8_t *aBuffer, uint16_t aBufferSize, uint16_t &aLength)
Error TxtData::Prepare(uint8_t *aBuffer,
uint16_t aBufferSize,
uint16_t &aLength,
bool aAddVendorName,
bool aAddVendorModel)
{
Error error = kErrorNone;
Dns::TxtDataEncoder encoder(aBuffer, aBufferSize);
@@ -152,6 +158,16 @@ Error TxtData::Prepare(uint8_t *aBuffer, uint16_t aBufferSize, uint16_t &aLength
}
#endif
if (aAddVendorName)
{
SuccessOrExit(error = encoder.AppendStringEntry(Key::kVendorName, Get<VendorInfo>().GetName()));
}
if (aAddVendorModel)
{
SuccessOrExit(error = encoder.AppendStringEntry(Key::kModelName, Get<VendorInfo>().GetModel()));
}
aLength = encoder.GetLength();
exit:
@@ -160,7 +176,8 @@ exit:
Error TxtData::Prepare(ServiceTxtData &aTxtData)
{
return Prepare(aTxtData.mData, sizeof(aTxtData.mData), aTxtData.mLength);
return Prepare(aTxtData.mData, sizeof(aTxtData.mData), aTxtData.mLength, /* aAddVendorName */ false,
/* aAddVendorModel */ false);
}
void TxtData::SetChangedCallback(ChangedCallback aCallback, void *aContext)
@@ -194,18 +211,98 @@ exit:
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
void TxtData::PrepareWithVendorData(Heap::Data &aTxtData)
{
uint16_t size;
uint16_t length;
uint8_t *buffer;
// Allocate a large enough buffer to fit both the base TXT data
// generated by the Border Agent and the vendor extra TXT data.
// The vendor TXT data is appended at the end. If `mVendorData`
// does not contain vendor name or model entries, they are
// populated from `VendorInfo` and room is allocated for them.
size = kMaxSizeNoVendorData + mVendorData.GetLength();
if (mShouldAddVendorName)
{
size += Tlv::kMaxVendorNameLength + sizeof(Key::kVendorName) + sizeof('=');
}
if (mShouldAddVendorModel)
{
size += Tlv::kMaxVendorModelLength + sizeof(Key::kModelName) + sizeof('=');
}
buffer = reinterpret_cast<uint8_t *>(Heap::CAlloc(size, sizeof(uint8_t)));
OT_ASSERT(buffer != nullptr);
SuccessOrAssert(Prepare(buffer, size, length, mShouldAddVendorName, mShouldAddVendorModel));
if (mVendorData.GetLength() != 0)
{
mVendorData.CopyBytesTo(buffer + length);
length += mVendorData.GetLength();
}
aTxtData.TakeFrom(buffer, length);
}
void TxtData::SetVendorData(const uint8_t *aVendorData, uint16_t aVendorDataLength)
{
Dns::TxtEntry entry;
Dns::TxtEntry::Iterator iterator;
VerifyOrExit(!mVendorData.Matches(aVendorData, aVendorDataLength));
SuccessOrAssert(mVendorData.SetFrom(aVendorData, aVendorDataLength));
// Check whether the provided `mVendorData` contains vendor name
// or model entries. If not present, the values from `VendorInfo`
// will be used and encoded in the TXT data.
mShouldAddVendorName = true;
mShouldAddVendorModel = true;
iterator.Init(mVendorData.GetBytes(), mVendorData.GetLength());
while (iterator.GetNextEntry(entry) == kErrorNone)
{
if (entry.MatchesKey(Key::kVendorName))
{
mShouldAddVendorName = false;
}
else if (entry.MatchesKey(Key::kModelName))
{
mShouldAddVendorModel = false;
}
}
Refresh();
exit:
return;
}
#endif
void TxtData::HandleVendorNameChange(void)
{
if (mShouldAddVendorName)
{
Refresh();
}
}
void TxtData::HandleVendorModelChange(void)
{
if (mShouldAddVendorModel)
{
Refresh();
}
}
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
uint32_t TxtData::StateBitmap::Determine(Instance &aInstance)
{
+18 -17
View File
@@ -51,6 +51,7 @@
#include "common/type_traits.hpp"
#include "net/dns_types.hpp"
#include "net/ip6_address.hpp"
#include "thread/vendor_info.hpp"
namespace ot {
namespace MeshCoP {
@@ -61,6 +62,7 @@ namespace BorderAgent {
class TxtData : public InstanceLocator
{
friend class ot::Notifier;
friend class ot::VendorInfo;
public:
typedef otBorderAgentConnMode ConnMode; ///< Connection Mode in a Border Agent State Bitmap.
@@ -206,18 +208,6 @@ public:
typedef otBorderAgentMeshCoPServiceTxtData ServiceTxtData; ///< Service TXT Data.
typedef otBorderAgentMeshCoPServiceChangedCallback ChangedCallback; ///< Service TXT Data changed callback.
/**
* Prepares the MeshCoP service TXT data.
*
* @param[out] aBuffer A pointer to a buffer to store the TXT data.
* @param[in] aBufferSize The size of @p aBuffer.
* @param[out] aLength On exit, the length of the prepared TXT data.
*
* @retval kErrorNone Successfully prepared the TXT data.
* @retval kErrorNoBufs The @p aBufferSize is too small.
*/
Error Prepare(uint8_t *aBuffer, uint16_t aBufferSize, uint16_t &aLength);
/**
* Prepares the MeshCoP service TXT data.
*
@@ -253,11 +243,15 @@ public:
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
/**
* Returns the vendor TXT data.
* Prepares the MeshCoP service TXT data including the vendor extra TXT data.
*
* @returns The vendor TXT data.
* The TXT data generated by the Border Agent itself is appended with the vendor extra TXT data (if any).
* If the vendor extra TXT data does not include vendor name or model entries, they are added automatically
* using the values from `VendorInfo`.
*
* @param[out] aTxtData A reference to a `Heap::Data` object to output the prepared TXT data.
*/
const Heap::Data &GetVendorData(void) const { return mVendorData; }
void PrepareWithVendorData(Heap::Data &aTxtData);
/**
* Sets the vendor extra TXT data to be included when the Border Agent advertises the mDNS `_meshcop._udp` service.
@@ -285,6 +279,8 @@ public:
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
private:
static constexpr uint16_t kMaxSizeNoVendorData = OT_BORDER_AGENT_MESHCOP_SERVICE_TXT_DATA_MAX_LENGTH;
static const char kRecordVersion[];
struct Key
@@ -302,10 +298,8 @@ private:
static const char kBbrPort[];
static const char kOmrPrefix[];
static const char kExtAddress[];
#if OPENTHREAD_CONFIG_BORDER_AGENT_TXT_DATA_PARSER_ENABLE
static const char kVendorName[];
static const char kModelName[];
#endif
};
struct StateBitmap
@@ -363,9 +357,14 @@ private:
#endif
};
Error Prepare(uint8_t *aBuffer, uint16_t aBufferSize, uint16_t &aLength, bool aAddVendorName, bool aAddVendorModel);
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
// Callback from Notifier
void HandleNotifierEvents(Events aEvents);
// Callbacks from VendorInfo
void HandleVendorNameChange(void);
void HandleVendorModelChange(void);
void HandleChangedTask(void);
@@ -375,6 +374,8 @@ private:
ChangedTask mChangedTask;
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
Heap::Data mVendorData;
bool mShouldAddVendorName;
bool mShouldAddVendorModel;
#endif
#endif
};
+22 -2
View File
@@ -69,7 +69,9 @@ VendorInfo::VendorInfo(Instance &aInstance)
Error VendorInfo::SetName(const char *aName)
{
Error error;
Error error = kErrorNone;
VerifyOrExit(!StringMatch(mName, (aName == nullptr) ? "" : aName));
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
VerifyOrExit(aName != nullptr && StringStartsWith(aName, kNamePrefix), error = kErrorInvalidArgs);
@@ -77,11 +79,29 @@ Error VendorInfo::SetName(const char *aName)
SuccessOrExit(error = StringCopy(mName, aName, kStringCheckUtf8Encoding));
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
Get<MeshCoP::BorderAgent::TxtData>().HandleVendorNameChange();
#endif
exit:
return error;
}
Error VendorInfo::SetModel(const char *aModel) { return StringCopy(mModel, aModel, kStringCheckUtf8Encoding); }
Error VendorInfo::SetModel(const char *aModel)
{
Error error = kErrorNone;
VerifyOrExit(!StringMatch(mModel, (aModel == nullptr) ? "" : aModel));
SuccessOrExit(error = StringCopy(mModel, aModel, kStringCheckUtf8Encoding));
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
Get<MeshCoP::BorderAgent::TxtData>().HandleVendorModelChange();
#endif
exit:
return error;
}
Error VendorInfo::SetSwVersion(const char *aSwVersion)
{
+93 -8
View File
@@ -1293,7 +1293,7 @@ struct TxtData
uint16_t mLength;
};
void ValidateMeshCoPTxtData(TxtData &aTxtData, Node &aNode)
void ValidateMeshCoPTxtData(TxtData &aTxtData, Node &aNode, bool aExpectVendorInfo, const char *aVendorName = nullptr)
{
// State bitmap masks and field values
static constexpr uint32_t kMaskConnectionMode = 7 << 0;
@@ -1447,6 +1447,28 @@ void ValidateMeshCoPTxtData(TxtData &aTxtData, Node &aNode)
VerifyOrQuit(!(stateBitmap & kFlagAdmitterSupported));
VerifyOrQuit(!info.mStateBitmap.mAdmitterSupported);
}
if (aExpectVendorInfo)
{
const char *expectedVendorName = (aVendorName != nullptr) ? aVendorName : aNode.Get<VendorInfo>().GetName();
const char *expectedModelName = aNode.Get<VendorInfo>().GetModel();
aTxtData.ValidateKey("vn", expectedVendorName);
VerifyOrQuit(info.mHasVendorName);
VerifyOrQuit(StringMatch(info.mVendorName, expectedVendorName));
aTxtData.ValidateKey("mn", expectedModelName);
VerifyOrQuit(info.mHasModelName);
VerifyOrQuit(StringMatch(info.mModelName, expectedModelName));
}
else
{
VerifyOrQuit(!aTxtData.ContainsKey("vn"));
VerifyOrQuit(!info.mHasVendorName);
VerifyOrQuit(!aTxtData.ContainsKey("mn"));
VerifyOrQuit(!info.mHasModelName);
}
}
//----------------------------------------------------------------------------------------------------------------------
@@ -1466,7 +1488,7 @@ void ReadAndValidateMeshCoPTxtData(Node &aNode)
SuccessOrQuit(aNode.Get<BaTxtData>().Prepare(serviceTxtData));
txtData.Init(serviceTxtData.mData, serviceTxtData.mLength);
ValidateMeshCoPTxtData(txtData, aNode);
ValidateMeshCoPTxtData(txtData, aNode, /* aExpectVendorInfo */ false);
}
void TestBorderAgentTxtDataCallback(void)
@@ -1553,7 +1575,7 @@ void TestBorderAgentTxtDataCallback(void)
static constexpr uint32_t kInfraIfIndex = 1;
static constexpr uint16_t kMaxEntries = 5;
static constexpr uint16_t kMaxTxtDataSize = 128;
static constexpr uint16_t kMaxTxtDataSize = 400;
typedef Dns::Name::Buffer DnsName;
@@ -1626,12 +1648,14 @@ void HandleTxtCallback(otInstance *aInstance, const Dns::Multicast::Core::TxtRes
outcome->mTtl = aResult->mTtl;
}
void ValidateRegisteredServiceData(Dns::Multicast::Core::Service &aService, Node &aNode)
void ValidateRegisteredServiceData(Dns::Multicast::Core::Service &aService,
Node &aNode,
const char *aVendorName = nullptr)
{
TxtData txtData;
txtData.Init(aService.mTxtData, aService.mTxtDataLength);
ValidateMeshCoPTxtData(txtData, aNode);
ValidateMeshCoPTxtData(txtData, aNode, /* aExpectVendorInfo */ true, aVendorName);
}
void TestBorderAgentServiceRegistration(void)
@@ -1749,7 +1773,7 @@ void TestBorderAgentServiceRegistration(void)
VerifyOrQuit(sTxtOutcomes.GetLength() == 1);
VerifyOrQuit(sTxtOutcomes[0].mTtl > 0);
txtData.Init(sTxtOutcomes[0].mTxtData, sTxtOutcomes[0].mTxtDataLength);
ValidateMeshCoPTxtData(txtData, node0);
ValidateMeshCoPTxtData(txtData, node0, /* aExptecVendorInfo */ true);
sBrowseOutcomes.Clear();
sSrvOutcomes.Clear();
@@ -2086,11 +2110,10 @@ void TestBorderAgentServiceRegistration(void)
VerifyOrQuit(service.mTtl > 0);
VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex);
VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED);
ValidateRegisteredServiceData(service, node0);
ValidateRegisteredServiceData(service, node0, /* aExpectedVendorName */ "nexus");
// Check that vendor TXT data is included at the end of
// the registered service TXT data.
VerifyOrQuit(service.mTxtDataLength > txtDataLengthWithNoVendorData);
VerifyOrQuit(service.mTxtDataLength > sizeof(kVendorTxtData));
VerifyOrQuit(!memcmp(&service.mTxtData[service.mTxtDataLength - sizeof(kVendorTxtData)], kVendorTxtData,
sizeof(kVendorTxtData)));
@@ -2131,6 +2154,68 @@ void TestBorderAgentServiceRegistration(void)
VerifyOrQuit(node0.Get<Dns::Multicast::Core>().GetNextService(*iterator, service, entryState) == kErrorNotFound);
node0.Get<Dns::Multicast::Core>().FreeIterator(*iterator);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Change vendor name and validate that the registered mDNS service is updated accordingly");
SuccessOrQuit(node0.Get<VendorInfo>().SetName("RD:v"));
nexus.AdvanceTime(Time::kOneSecondInMsec);
iterator = node0.Get<Dns::Multicast::Core>().AllocateIterator();
VerifyOrQuit(iterator != nullptr);
SuccessOrQuit(node0.Get<Dns::Multicast::Core>().GetNextService(*iterator, service, entryState));
Log(" HostName: %s", service.mHostName);
Log(" ServiceInstance: %s", service.mServiceInstance);
Log(" ServiceType: %s", service.mServiceType);
Log(" Port: %u", service.mPort);
Log(" TTL: %lu", ToUlong(service.mTtl));
VerifyOrQuit(StringMatch(service.mServiceType, "_meshcop._udp"));
VerifyOrQuit(StringStartsWith(service.mServiceInstance, "OpenThreadAgent"));
VerifyOrQuit(StringStartsWith(service.mHostName, "ot"));
VerifyOrQuit(service.mSubTypeLabelsLength == 0);
VerifyOrQuit(service.mPort == node0.Get<MeshCoP::BorderAgent::Manager>().GetUdpPort());
VerifyOrQuit(service.mTtl > 0);
VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex);
VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED);
ValidateRegisteredServiceData(service, node0);
// Check that there is no more registered mDNS service
VerifyOrQuit(node0.Get<Dns::Multicast::Core>().GetNextService(*iterator, service, entryState) == kErrorNotFound);
node0.Get<Dns::Multicast::Core>().FreeIterator(*iterator);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Change vendor model and validate that the registered mDNS service is updated accordingly");
SuccessOrQuit(node0.Get<VendorInfo>().SetModel("model"));
nexus.AdvanceTime(Time::kOneSecondInMsec);
iterator = node0.Get<Dns::Multicast::Core>().AllocateIterator();
VerifyOrQuit(iterator != nullptr);
SuccessOrQuit(node0.Get<Dns::Multicast::Core>().GetNextService(*iterator, service, entryState));
Log(" HostName: %s", service.mHostName);
Log(" ServiceInstance: %s", service.mServiceInstance);
Log(" ServiceType: %s", service.mServiceType);
Log(" Port: %u", service.mPort);
Log(" TTL: %lu", ToUlong(service.mTtl));
VerifyOrQuit(StringMatch(service.mServiceType, "_meshcop._udp"));
VerifyOrQuit(StringStartsWith(service.mServiceInstance, "OpenThreadAgent"));
VerifyOrQuit(StringStartsWith(service.mHostName, "ot"));
VerifyOrQuit(service.mSubTypeLabelsLength == 0);
VerifyOrQuit(service.mPort == node0.Get<MeshCoP::BorderAgent::Manager>().GetUdpPort());
VerifyOrQuit(service.mTtl > 0);
VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex);
VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED);
ValidateRegisteredServiceData(service, node0);
// Check that there is no more registered mDNS service
VerifyOrQuit(node0.Get<Dns::Multicast::Core>().GetNextService(*iterator, service, entryState) == kErrorNotFound);
node0.Get<Dns::Multicast::Core>().FreeIterator(*iterator);
}
} // namespace Nexus