[border-agent] new API to set vendor-specific extra TXT data (#11487)

This commit introduces a new API to allow setting vendor-specific
extra TXT data to be included when the Border Agent advertises its
mDNS `_meshcop._udp` service.

The provided vendor TXT data is appended as given to the TXT data
generated by the Border Agent and included in the `_meshcop._udp`
mDNS service advertisement.

This vendor TXT data can be set at any time, regardless of the Border
Agent's state. Any change from a previously set value will trigger an
update of the registered mDNS service to advertise the new TXT data.

This commit also updates `test_border_agent` by generalizing and
simplifying the validation of the Border Agent's `_meshcop._udp`
service TXT data. The tests are also expanded to validate the
complete registered service TXT data, including the newly added
support for vendor-specific extra TXT data.
This commit is contained in:
Abtin Keshavarzian
2025-05-13 17:56:36 -07:00
committed by GitHub
parent 7b1fdbf65f
commit 8244bc7fad
6 changed files with 397 additions and 154 deletions
+31 -1
View File
@@ -128,7 +128,7 @@ typedef struct otBorderAgentSessionIterator
* Border Agent Id (id) - 4 + 16 = 20 bytes
* Network Name (nn) - 4 + 16 = 20 bytes
* ExtendedPanId (xp) - 4 + 8 = 12 bytes
* THread Version (tv) - 4 + 5 = 9 bytes
* Thread Version (tv) - 4 + 5 = 9 bytes
* Extended Address (xa) - 4 + 8 = 12 bytes
* State Bitmap (sb) - 4 + 4 = 8 bytes
* Partition Id (pt) - 4 + 4 = 8 bytes
@@ -235,6 +235,12 @@ void otBorderAgentSetMeshCoPServiceChangedCallback(otInstance
/**
* Gets the MeshCoP service TXT data.
*
* The generated TXT data includes a subset of keys (depending on the device's current state and whether features are
* enabled) as specified in the documentation of the `OT_BORDER_AGENT_MESHCOP_SERVICE_TXT_DATA_MAX_LENGTH` constant.
* Notably, if `OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE` is enabled and `otBorderAgentSetVendorTxtData()`
* was used to set extra vendor-specific TXT data bytes, those vendor-specified TXT data bytes are NOT included in the
* TXT data returned by this function.
*
* @param[in] aInstance A pointer to an OpenThread instance.
* @param[out] aTxtData A pointer to a MeshCoP Service TXT data struct to get the data.
*
@@ -278,6 +284,30 @@ otError otBorderAgentGetMeshCoPServiceTxtData(otInstance *aInstance, otBorderAge
*/
otError otBorderAgentSetMeshCoPServiceBaseName(otInstance *aInstance, const char *aBaseName);
/**
* Sets the vendor extra TXT data to be included when the Border Agent advertises the mDNS `_meshcop._udp` service.
*
* Requires the `OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE` feature.
*
* The provided @p aVendorData bytes are appended as they appear in the buffer to the end of the TXT data generated by
* the Border Agent itself, and are then included in the advertised mDNS `_meshcop._udp` service.
*
* This function itself does not perform any validation of the format of the provided @p aVendorData. Therefore, the
* caller MUST ensure it is formatted properly. Per the Thread specification, vendor-specific Key-Value TXT data pairs
* use TXT keys starting with 'v'. For example, `vn` for vendor name and generally `v*`.
*
* The OpenThread stack will create and retain its own copy of the bytes in @p aVendorData. So, the buffer passed to
* this function does not need to persist beyond the scope of the call.
*
* The vendor TXT data can be set at any time while the Border Agent is in any state. If there is a change from the
* previously set value, it will trigger an update of the registered mDNS service to advertise the new TXT data.
*
* @param[in] aInstance The OpenThread instance.
* @param[in] aVendorData A pointer to the buffer containing the vendor TXT data.
* @param[in] aVendorDataLength The length of @p aVendorData in bytes.
*/
void otBorderAgentSetVendorTxtData(otInstance *aInstance, const uint8_t *aVendorData, uint16_t aVendorDataLength);
/**
* Gets the randomly generated Border Agent ID.
*
+1 -1
View File
@@ -52,7 +52,7 @@ extern "C" {
*
* @note This number versions both OpenThread platform and user APIs.
*/
#define OPENTHREAD_API_VERSION (506)
#define OPENTHREAD_API_VERSION (507)
/**
* @addtogroup api-instance
+5
View File
@@ -64,6 +64,11 @@ otError otBorderAgentSetMeshCoPServiceBaseName(otInstance *aInstance, const char
return AsCoreType(aInstance).Get<MeshCoP::BorderAgent>().SetServiceBaseName(aBaseName);
}
void otBorderAgentSetVendorTxtData(otInstance *aInstance, const uint8_t *aVendorData, uint16_t aVendorDataLength)
{
AsCoreType(aInstance).Get<MeshCoP::BorderAgent>().SetVendorTxtData(aVendorData, aVendorDataLength);
}
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE
+41 -6
View File
@@ -434,6 +434,17 @@ exit:
return error;
}
void BorderAgent::SetVendorTxtData(const uint8_t *aVendorData, uint16_t aVendorDataLength)
{
VerifyOrExit(!mVendorTxtData.Matches(aVendorData, aVendorDataLength));
SuccessOrAssert(mVendorTxtData.SetFrom(aVendorData, aVendorDataLength));
PostServiceTask();
exit:
return;
}
const char *BorderAgent::GetServiceName(void)
{
if (IsServiceNameEmpty())
@@ -453,22 +464,41 @@ void BorderAgent::ConstrcutServiceName(const char *aBaseName, Dns::Name::LabelBu
void BorderAgent::RegisterService(void)
{
ServiceTxtData txtData;
Dnssd::Service service;
uint8_t *txtDataBuffer;
uint16_t txtDataBufferSize;
uint16_t txtDataLength;
VerifyOrExit(Get<Dnssd>().IsReady());
SuccessOrAssert(PrepareServiceTxtData(txtData));
// 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.
txtDataBufferSize = kTxtDataMaxSize + mVendorTxtData.GetLength();
txtDataBuffer = reinterpret_cast<uint8_t *>(Heap::CAlloc(txtDataBufferSize, sizeof(uint8_t)));
OT_ASSERT(txtDataBuffer != nullptr);
SuccessOrAssert(PrepareServiceTxtData(txtDataBuffer, txtDataBufferSize, txtDataLength));
if (mVendorTxtData.GetLength() != 0)
{
mVendorTxtData.CopyBytesTo(txtDataBuffer + txtDataLength);
txtDataLength += mVendorTxtData.GetLength();
}
service.Clear();
service.mServiceInstance = GetServiceName();
service.mServiceType = kServiceType;
service.mPort = IsRunning() ? GetUdpPort() : kDummyUdpPort;
service.mTxtData = txtData.mData;
service.mTxtDataLength = txtData.mLength;
service.mTxtData = txtDataBuffer;
service.mTxtDataLength = txtDataLength;
Get<Dnssd>().RegisterService(service, /* aRequestId */ 0, /* aCallback */ nullptr);
Heap::Free(txtDataBuffer);
exit:
return;
}
@@ -493,9 +523,14 @@ exit:
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
Error BorderAgent::PrepareServiceTxtData(ServiceTxtData &aTxtData)
{
return PrepareServiceTxtData(aTxtData.mData, sizeof(aTxtData.mData), aTxtData.mLength);
}
Error BorderAgent::PrepareServiceTxtData(uint8_t *aBuffer, uint16_t aBufferSize, uint16_t &aLength)
{
Error error = kErrorNone;
Dns::TxtDataEncoder encoder(aTxtData.mData, sizeof(aTxtData.mData));
Dns::TxtDataEncoder encoder(aBuffer, aBufferSize);
#if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE
{
@@ -555,7 +590,7 @@ Error BorderAgent::PrepareServiceTxtData(ServiceTxtData &aTxtData)
}
#endif
aTxtData.mLength = encoder.GetLength();
aLength = encoder.GetLength();
exit:
return error;
+25
View File
@@ -44,6 +44,7 @@
#include "common/appender.hpp"
#include "common/as_core_type.hpp"
#include "common/heap_allocatable.hpp"
#include "common/heap_data.hpp"
#include "common/linked_list.hpp"
#include "common/locator.hpp"
#include "common/non_copyable.hpp"
@@ -255,6 +256,27 @@ public:
* @retval kErrorInvalidArgs The name is too long or invalid.
*/
Error SetServiceBaseName(const char *aBaseName);
/**
* Sets the vendor extra TXT data to be included when the Border Agent advertises the mDNS `_meshcop._udp` service.
*
* The provided @p aVendorData bytes are appended as they appear in the buffer to the end of the TXT data generated
* by the Border Agent itself, and are then included in the advertised mDNS `_meshcop._udp` service.
*
* This method itself does not perform any validation of the format of the provided @p aVendorData. Therefore, the
* caller MUST ensure it is formatted properly. Per the Thread specification, vendor-specific Key-Value TXT data
* pairs use TXT keys starting with 'v'. For example, `vn` for vendor name.
*
* The `BorderAgent` will create and retain its own copy of the bytes in @p aVendorData. So, the buffer passed to
* this method does not need to persist beyond the scope of the call.
*
* The vendor TXT data can be set at any time while the Border Agent is in any state. If there is a change from the
* previously set value, it will trigger an update of the registered mDNS service to advertise the new TXT data.
*
* @param[in] aVendorData A pointer to the buffer containing the vendor TXT data.
* @param[in] aVendorDataLength The length of @p aVendorData in bytes.
*/
void SetVendorTxtData(const uint8_t *aVendorData, uint16_t aVendorDataLength);
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
@@ -438,6 +460,7 @@ 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;
@@ -576,6 +599,7 @@ private:
static Coap::Message::Code CoapCodeFromError(Error aError);
Error PrepareServiceTxtData(uint8_t *aBuffer, uint16_t aBufferSize, uint16_t &aLength);
uint32_t DetermineStateBitmap(void) const;
void PostServiceTask(void);
@@ -615,6 +639,7 @@ private:
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE
Dns::Name::LabelBuffer mServiceName;
Heap::Data mVendorTxtData;
#endif
Counters mCounters;
};
+294 -146
View File
@@ -756,36 +756,107 @@ void TestBorderAgentEphemeralKey(void)
//----------------------------------------------------------------------------------------------------------------------
class TxtDataTester
struct TxtData
{
public:
TxtDataTester(BorderAgent &aBorderAgent)
: mBorderAgent(aBorderAgent)
, mIsRunning(false)
, mUdpPort(0)
, mCallbackInvoked(false)
void Init(const uint8_t *aData, uint16_t aLength) { mData = aData, mLength = aLength; }
void ValidateFormat(void)
{
TxtEntry::Iterator iter;
TxtEntry txtEntry;
iter.Init(mData, mLength);
while (true)
{
Error error = iter.GetNextEntry(txtEntry);
if (error == kErrorNotFound)
{
break;
}
SuccessOrQuit(error);
VerifyOrQuit(txtEntry.mKey != nullptr);
}
}
static void HandleServiceChanged(void *aContext) { static_cast<TxtDataTester *>(aContext)->HandleServiceChanged(); }
void HandleServiceChanged(void)
void LogAllTxtEntries(void)
{
mIsRunning = mBorderAgent.IsRunning();
mUdpPort = mBorderAgent.GetUdpPort();
SuccessOrQuit(mBorderAgent.PrepareServiceTxtData(mTxtData));
mCallbackInvoked = true;
static constexpr uint16_t kValueStringSize = 256;
char valueString[kValueStringSize];
TxtEntry::Iterator iter;
TxtEntry txtEntry;
Log("TXT data - length %u", mLength);
iter.Init(mData, mLength);
while (true)
{
Error error = iter.GetNextEntry(txtEntry);
StringWriter writer(valueString, sizeof(valueString));
if (error == kErrorNotFound)
{
break;
}
SuccessOrQuit(error);
VerifyOrQuit(txtEntry.mKey != nullptr);
writer.AppendHexBytes(txtEntry.mValue, txtEntry.mValueLength);
Log(" %s -> [%s] (len:%u)", txtEntry.mKey, valueString, txtEntry.mValueLength);
}
}
bool FindTxtEntry(const char *aKey, TxtEntry &aTxtEntry)
bool ContainsKey(const char *aKey) const
{
TxtEntry txtEntry;
return FindTxtEntry(aKey, txtEntry);
}
void ValidateKey(const char *aKey, const void *aValue, uint16_t aValueLength) const
{
TxtEntry txtEntry;
VerifyOrQuit(FindTxtEntry(aKey, txtEntry));
VerifyOrQuit(txtEntry.mValueLength == aValueLength);
VerifyOrQuit(memcmp(txtEntry.mValue, aValue, aValueLength) == 0);
}
template <typename ObjectType> void ValidateKey(const char *aKey, const ObjectType &aObject) const
{
static_assert(!TypeTraits::IsPointer<ObjectType>::kValue, "ObjectType must not be a pointer");
ValidateKey(aKey, &aObject, sizeof(aObject));
}
void ValidateKey(const char *aKey, const char *aString) const { ValidateKey(aKey, aString, strlen(aString)); }
uint32_t ReadUint32Key(const char *aKey) const
{
TxtEntry txtEntry;
VerifyOrQuit(FindTxtEntry(aKey, txtEntry));
VerifyOrQuit(txtEntry.mValueLength == sizeof(uint32_t));
return BigEndian::ReadUint32(txtEntry.mValue);
}
bool FindTxtEntry(const char *aKey, TxtEntry &aTxtEntry) const
{
bool found = false;
TxtEntry::Iterator iter;
iter.Init(mTxtData.mData, mTxtData.mLength);
iter.Init(mData, mLength);
while (iter.GetNextEntry(aTxtEntry) == kErrorNone)
{
if (strcmp(aTxtEntry.mKey, aKey) == 0)
VerifyOrQuit(aTxtEntry.mKey != nullptr);
if (StringMatch(aTxtEntry.mKey, aKey))
{
found = true;
break;
@@ -795,27 +866,11 @@ public:
return found;
}
BorderAgent &mBorderAgent;
BorderAgent::ServiceTxtData mTxtData;
bool mIsRunning;
uint16_t mUdpPort;
bool mCallbackInvoked;
const uint8_t *mData;
uint16_t mLength;
};
template <typename ObjectType> bool CheckObjectSameAsTxtEntryData(const TxtEntry &aTxtEntry, const ObjectType &aObject)
{
static_assert(!TypeTraits::IsPointer<ObjectType>::kValue, "ObjectType must not be a pointer");
return aTxtEntry.mValueLength == sizeof(ObjectType) && memcmp(aTxtEntry.mValue, &aObject, sizeof(ObjectType)) == 0;
}
template <> bool CheckObjectSameAsTxtEntryData<NameData>(const TxtEntry &aTxtEntry, const NameData &aNameData)
{
return aTxtEntry.mValueLength == aNameData.GetLength() &&
memcmp(aTxtEntry.mValue, aNameData.GetBuffer(), aNameData.GetLength()) == 0;
}
void TestBorderAgentTxtDataCallback(void)
void ValidateMeshCoPTxtData(TxtData &aTxtData, Node &aNode)
{
// State bitmap masks and field values
static constexpr uint32_t kMaskConnectionMode = 7 << 0;
@@ -832,15 +887,100 @@ void TestBorderAgentTxtDataCallback(void)
static constexpr uint32_t kThreadRoleLeader = 3 << 9;
static constexpr uint32_t kFlagEpskcSupported = 1 << 11;
Core nexus;
Node &node0 = nexus.CreateNode();
TxtDataTester txtDataTester(node0.Get<BorderAgent>());
TxtEntry txtEntry;
uint32_t stateBitmap;
#if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE
BorderAgent::Id id;
uint32_t stateBitmap;
uint32_t threadIfStatus;
uint32_t threadRole;
aTxtData.ValidateFormat();
aTxtData.LogAllTxtEntries();
SuccessOrQuit(aNode.Get<BorderAgent>().GetId(id));
aTxtData.ValidateKey("id", id);
aTxtData.ValidateKey("nn", aNode.Get<NetworkNameManager>().GetNetworkName().GetAsCString());
aTxtData.ValidateKey("xp", aNode.Get<ExtendedPanIdManager>().GetExtPanId());
aTxtData.ValidateKey("tv", kThreadVersionString);
aTxtData.ValidateKey("xa", aNode.Get<Mac::Mac>().GetExtAddress());
if (aNode.Get<Mle::Mle>().IsAttached())
{
aTxtData.ValidateKey("pt", BigEndian::HostSwap32(aNode.Get<Mle::Mle>().GetLeaderData().GetPartitionId()));
aTxtData.ValidateKey("at", aNode.Get<ActiveDatasetManager>().GetTimestamp());
}
else
{
VerifyOrQuit(!aTxtData.ContainsKey("pt"));
VerifyOrQuit(!aTxtData.ContainsKey("at"));
}
stateBitmap = aTxtData.ReadUint32Key("sb");
VerifyOrQuit((stateBitmap & kMaskConnectionMode) == aNode.Get<BorderAgent>().IsRunning() ? kConnectionModePskc
: kConnectionModeDisabled);
switch (aNode.Get<Mle::Mle>().GetRole())
{
case Mle::DeviceRole::kRoleDisabled:
threadIfStatus = kThreadIfStatusNotInitialized;
threadRole = kThreadRoleDisabledOrDetached;
break;
case Mle::DeviceRole::kRoleDetached:
threadIfStatus = kThreadIfStatusInitialized;
threadRole = kThreadRoleDisabledOrDetached;
break;
case Mle::DeviceRole::kRoleChild:
threadIfStatus = kThreadIfStatusActive;
threadRole = kThreadRoleChild;
break;
case Mle::DeviceRole::kRoleRouter:
threadIfStatus = kThreadIfStatusActive;
threadRole = kThreadRoleRouter;
break;
case Mle::DeviceRole::kRoleLeader:
threadIfStatus = kThreadIfStatusActive;
threadRole = kThreadRoleLeader;
break;
}
VerifyOrQuit((stateBitmap & kMaskThreadIfStatus) == threadIfStatus);
VerifyOrQuit((stateBitmap & kMaskThreadRole) == threadRole);
if (aNode.Get<BorderAgent>().Get<EphemeralKeyManager>().GetState() !=
BorderAgent::EphemeralKeyManager::kStateDisabled)
{
VerifyOrQuit(stateBitmap & kFlagEpskcSupported);
}
else
{
VerifyOrQuit(!(stateBitmap & kFlagEpskcSupported));
}
}
//----------------------------------------------------------------------------------------------------------------------
void HandleServiceChanged(void *aContext) // Callback used in `TestBorderAgentTxtDataCallback().`
{
// `aContext` is a boolean `callbackInvoked`
VerifyOrQuit(aContext != nullptr);
*static_cast<bool *>(aContext) = true;
}
void ReadAndValidateMeshCoPTxtData(Node &aNode)
{
BorderAgent::ServiceTxtData serviceTxtData;
TxtData txtData;
SuccessOrQuit(aNode.Get<BorderAgent>().PrepareServiceTxtData(serviceTxtData));
txtData.Init(serviceTxtData.mData, serviceTxtData.mLength);
ValidateMeshCoPTxtData(txtData, aNode);
}
void TestBorderAgentTxtDataCallback(void)
{
Core nexus;
Node &node0 = nexus.CreateNode();
bool callbackInvoked = false;
BorderAgent::Id newId;
#endif
Log("------------------------------------------------------------------------------------------------------");
Log("TestBorderAgentTxtDataCallback");
@@ -848,152 +988,88 @@ void TestBorderAgentTxtDataCallback(void)
nexus.AdvanceTime(0);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// 1. Set MeshCoP service change callback. Will get initial values.
// Set MeshCoP service change callback. Will get initial values.
Log("Set MeshCoP service change callback and check initial values");
node0.Get<BorderAgent>().SetServiceChangedCallback(TxtDataTester::HandleServiceChanged, &txtDataTester);
node0.Get<BorderAgent>().SetServiceChangedCallback(HandleServiceChanged, &callbackInvoked);
nexus.AdvanceTime(1);
// 1.1 Check the initial TXT entries
VerifyOrQuit(txtDataTester.mCallbackInvoked);
#if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE
VerifyOrQuit(txtDataTester.FindTxtEntry("id", txtEntry));
VerifyOrQuit(node0.Get<BorderAgent>().GetId(id) == kErrorNone);
VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, id));
#endif
VerifyOrQuit(txtDataTester.FindTxtEntry("nn", txtEntry));
VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, node0.Get<NetworkNameManager>().GetNetworkName().GetAsData()));
VerifyOrQuit(txtDataTester.FindTxtEntry("xp", txtEntry));
VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, node0.Get<ExtendedPanIdManager>().GetExtPanId()));
VerifyOrQuit(txtDataTester.FindTxtEntry("tv", txtEntry));
VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, NameData(kThreadVersionString, strlen(kThreadVersionString))));
VerifyOrQuit(txtDataTester.FindTxtEntry("xa", txtEntry));
VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, node0.Get<Mac::Mac>().GetExtAddress()));
// Check the initial TXT entries
ReadAndValidateMeshCoPTxtData(node0);
VerifyOrQuit(txtDataTester.FindTxtEntry("sb", txtEntry));
VerifyOrQuit(txtEntry.mValueLength == sizeof(uint32_t));
stateBitmap = BigEndian::ReadUint32(txtEntry.mValue);
VerifyOrQuit((stateBitmap & kMaskConnectionMode) == kConnectionModeDisabled);
VerifyOrQuit((stateBitmap & kMaskThreadIfStatus) == kThreadIfStatusNotInitialized);
VerifyOrQuit((stateBitmap & kMaskThreadRole) == kThreadRoleDisabledOrDetached);
VerifyOrQuit(stateBitmap & kFlagEpskcSupported);
VerifyOrQuit(txtDataTester.FindTxtEntry("pt", txtEntry) == false);
VerifyOrQuit(txtDataTester.FindTxtEntry("at", txtEntry) == false);
// 1.2 Check the Border Agent state
VerifyOrQuit(txtDataTester.mIsRunning == false);
VerifyOrQuit(txtDataTester.mUdpPort == 0);
// Check the Border Agent state
VerifyOrQuit(!node0.Get<BorderAgent>().IsRunning());
VerifyOrQuit(node0.Get<BorderAgent>().GetUdpPort() == 0);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// 2. Join Thread network and check updated values and states.
txtDataTester.mCallbackInvoked = false;
// Join Thread network and check updated values and states.
callbackInvoked = false;
Log("Join Thread network and check updated Txt data and states");
node0.Form();
nexus.AdvanceTime(50 * Time::kOneSecondInMsec);
// 2.1 Check the initial TXT entries
VerifyOrQuit(txtDataTester.mCallbackInvoked);
#if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE
VerifyOrQuit(txtDataTester.FindTxtEntry("id", txtEntry));
VerifyOrQuit(node0.Get<BorderAgent>().GetId(id) == kErrorNone);
VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, id));
#endif
VerifyOrQuit(txtDataTester.FindTxtEntry("nn", txtEntry));
VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, node0.Get<NetworkNameManager>().GetNetworkName().GetAsData()));
VerifyOrQuit(txtDataTester.FindTxtEntry("xp", txtEntry));
VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, node0.Get<ExtendedPanIdManager>().GetExtPanId()));
VerifyOrQuit(txtDataTester.FindTxtEntry("tv", txtEntry));
VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, NameData(kThreadVersionString, strlen(kThreadVersionString))));
VerifyOrQuit(txtDataTester.FindTxtEntry("xa", txtEntry));
VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, node0.Get<Mac::Mac>().GetExtAddress()));
VerifyOrQuit(txtDataTester.FindTxtEntry("pt", txtEntry));
VerifyOrQuit(CheckObjectSameAsTxtEntryData(
txtEntry, BigEndian::HostSwap32(node0.Get<Mle::Mle>().GetLeaderData().GetPartitionId())));
VerifyOrQuit(txtDataTester.FindTxtEntry("at", txtEntry));
VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, node0.Get<ActiveDatasetManager>().GetTimestamp()));
VerifyOrQuit(callbackInvoked);
ReadAndValidateMeshCoPTxtData(node0);
VerifyOrQuit(txtDataTester.FindTxtEntry("sb", txtEntry));
VerifyOrQuit(txtEntry.mValueLength == sizeof(uint32_t));
stateBitmap = BigEndian::ReadUint32(txtEntry.mValue);
VerifyOrQuit((stateBitmap & kMaskConnectionMode) == kConnectionModePskc);
VerifyOrQuit((stateBitmap & kMaskThreadIfStatus) == kThreadIfStatusActive);
VerifyOrQuit((stateBitmap & kMaskThreadRole) == kThreadRoleLeader);
VerifyOrQuit(stateBitmap & kFlagEpskcSupported);
// 2.2 Check the Border Agent state
VerifyOrQuit(txtDataTester.mIsRunning == true);
VerifyOrQuit(txtDataTester.mUdpPort != 0);
VerifyOrQuit(node0.Get<BorderAgent>().IsRunning());
VerifyOrQuit(node0.Get<BorderAgent>().GetUdpPort() != 0);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE
Log("Change the Border Agent ID and validate that TXT data changed and callback is invoked");
newId.GenerateRandom();
VerifyOrQuit(newId != id);
txtDataTester.mCallbackInvoked = false;
callbackInvoked = false;
SuccessOrQuit(node0.Get<BorderAgent>().SetId(newId));
nexus.AdvanceTime(1);
VerifyOrQuit(txtDataTester.mCallbackInvoked);
VerifyOrQuit(txtDataTester.FindTxtEntry("id", txtEntry));
VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, newId));
ReadAndValidateMeshCoPTxtData(node0);
// Validate that setting the ID to the same value as before is
// correctly detected and does not trigger the callback.
txtDataTester.mCallbackInvoked = false;
callbackInvoked = false;
SuccessOrQuit(node0.Get<BorderAgent>().SetId(newId));
nexus.AdvanceTime(1);
VerifyOrQuit(!txtDataTester.mCallbackInvoked);
#endif // OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE
VerifyOrQuit(!callbackInvoked);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Disable EphemeralKeyManager and validate that TXT data state bitmap indicates this");
txtDataTester.mCallbackInvoked = false;
callbackInvoked = false;
node0.Get<EphemeralKeyManager>().SetEnabled(false);
VerifyOrQuit(node0.Get<EphemeralKeyManager>().GetState() == EphemeralKeyManager::kStateDisabled);
nexus.AdvanceTime(1);
VerifyOrQuit(txtDataTester.mCallbackInvoked);
VerifyOrQuit(txtDataTester.FindTxtEntry("sb", txtEntry));
VerifyOrQuit(txtEntry.mValueLength == sizeof(uint32_t));
stateBitmap = BigEndian::ReadUint32(txtEntry.mValue);
VerifyOrQuit((stateBitmap & kMaskConnectionMode) == kConnectionModePskc);
VerifyOrQuit((stateBitmap & kMaskThreadIfStatus) == kThreadIfStatusActive);
VerifyOrQuit((stateBitmap & kMaskThreadRole) == kThreadRoleLeader);
VerifyOrQuit(!(stateBitmap & kFlagEpskcSupported));
VerifyOrQuit(callbackInvoked);
ReadAndValidateMeshCoPTxtData(node0);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Disable the MLE operation and validate the TXT data state bitmap");
txtDataTester.mCallbackInvoked = false;
callbackInvoked = false;
SuccessOrQuit(node0.Get<Mle::Mle>().Disable());
nexus.AdvanceTime(1);
VerifyOrQuit(txtDataTester.mCallbackInvoked);
VerifyOrQuit(txtDataTester.FindTxtEntry("sb", txtEntry));
VerifyOrQuit(txtEntry.mValueLength == sizeof(uint32_t));
stateBitmap = BigEndian::ReadUint32(txtEntry.mValue);
VerifyOrQuit((stateBitmap & kMaskConnectionMode) == kConnectionModeDisabled);
VerifyOrQuit((stateBitmap & kMaskThreadIfStatus) == kThreadIfStatusNotInitialized);
VerifyOrQuit((stateBitmap & kMaskThreadRole) == kThreadRoleDisabledOrDetached);
VerifyOrQuit(!(stateBitmap & kFlagEpskcSupported));
VerifyOrQuit(callbackInvoked);
ReadAndValidateMeshCoPTxtData(node0);
}
//----------------------------------------------------------------------------------------------------------------------
void ValidateRegisteredServiceData(Dns::Multicast::Core::Service &aService, Node &aNode)
{
TxtData txtData;
txtData.Init(aService.mTxtData, aService.mTxtDataLength);
ValidateMeshCoPTxtData(txtData, aNode);
}
void TestBorderAgentServiceRegisteration(void)
{
static const char kDefaultServiceBaseName[] = OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME;
static const char kEphemeralKey[] = "nexus1234";
static const char kDefaultServiceBaseName[] = OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME;
static const char kEphemeralKey[] = "nexus1234";
static const uint8_t kVendorTxtData[] = {8, 'v', 'n', '=', 'n', 'e', 'x', 'u', 's'};
static constexpr uint32_t kUdpPort = 49155;
static constexpr uint32_t kInfraIfIndex = 1;
@@ -1003,6 +1079,7 @@ void TestBorderAgentServiceRegisteration(void)
Dns::Multicast::Core::Iterator *iterator;
Dns::Multicast::Core::Service service;
Dns::Multicast::Core::EntryState entryState;
uint16_t txtDataLengthWithNoVendorData;
Log("------------------------------------------------------------------------------------------------------");
Log("TestBorderAgentServiceRegisteration");
@@ -1044,10 +1121,10 @@ void TestBorderAgentServiceRegisteration(void)
VerifyOrQuit(StringStartsWith(service.mHostName, "ot"));
VerifyOrQuit(service.mPort == node0.Get<MeshCoP::BorderAgent>().GetUdpPort());
VerifyOrQuit(service.mSubTypeLabelsLength == 0);
VerifyOrQuit(service.mTxtDataLength > 1);
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);
@@ -1093,8 +1170,8 @@ void TestBorderAgentServiceRegisteration(void)
if (StringMatch(service.mServiceType, "_meshcop._udp"))
{
VerifyOrQuit(service.mTxtDataLength > 1);
VerifyOrQuit(service.mPort == node0.Get<MeshCoP::BorderAgent>().GetUdpPort());
ValidateRegisteredServiceData(service, node0);
}
else if (StringMatch(service.mServiceType, "_meshcop-e._udp"))
{
@@ -1132,11 +1209,11 @@ void TestBorderAgentServiceRegisteration(void)
VerifyOrQuit(StringStartsWith(service.mServiceInstance, kDefaultServiceBaseName));
VerifyOrQuit(StringStartsWith(service.mHostName, "ot"));
VerifyOrQuit(service.mSubTypeLabelsLength == 0);
VerifyOrQuit(service.mTxtDataLength > 1);
VerifyOrQuit(service.mPort == node0.Get<MeshCoP::BorderAgent>().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);
@@ -1164,11 +1241,11 @@ void TestBorderAgentServiceRegisteration(void)
VerifyOrQuit(StringStartsWith(service.mServiceInstance, "OpenThreadAgent"));
VerifyOrQuit(StringStartsWith(service.mHostName, "ot"));
VerifyOrQuit(service.mSubTypeLabelsLength == 0);
VerifyOrQuit(service.mTxtDataLength > 1);
VerifyOrQuit(service.mPort == node0.Get<MeshCoP::BorderAgent>().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);
@@ -1212,11 +1289,82 @@ void TestBorderAgentServiceRegisteration(void)
VerifyOrQuit(StringStartsWith(service.mServiceInstance, "OpenThreadAgent"));
VerifyOrQuit(StringStartsWith(service.mHostName, "ot"));
VerifyOrQuit(service.mSubTypeLabelsLength == 0);
VerifyOrQuit(service.mTxtDataLength > 1);
VerifyOrQuit(service.mPort == node0.Get<MeshCoP::BorderAgent>().GetUdpPort());
VerifyOrQuit(service.mTtl > 0);
VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex);
VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED);
ValidateRegisteredServiceData(service, node0);
txtDataLengthWithNoVendorData = service.mTxtDataLength;
// 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("Set vendor TXT data and validate that it is included in the registered mDNS service");
node0.Get<BorderAgent>().SetVendorTxtData(kVendorTxtData, sizeof(kVendorTxtData));
nexus.AdvanceTime(5 * 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>().GetUdpPort());
VerifyOrQuit(service.mTtl > 0);
VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex);
VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED);
ValidateRegisteredServiceData(service, node0);
// 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)));
// 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("Clear vendor TXT data and validate that the registered mDNS service is updated accordingly");
node0.Get<BorderAgent>().SetVendorTxtData(nullptr, 0);
nexus.AdvanceTime(5 * 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>().GetUdpPort());
VerifyOrQuit(service.mTtl > 0);
VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex);
VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED);
ValidateRegisteredServiceData(service, node0);
VerifyOrQuit(service.mTxtDataLength == txtDataLengthWithNoVendorData);
// Check that there is no more registered mDNS service
VerifyOrQuit(node0.Get<Dns::Multicast::Core>().GetNextService(*iterator, service, entryState) == kErrorNotFound);