[dns] add common TxtDataEncoder (#11469)

This commit moves and enhances the `TxtDataEncoder` class, relocating
it to the common `dns_types.hpp` header file.

The new `TxtDataEncoder` provides helper methods to append TXT entries
with a variety of value types, including `NameData`, C-strings, or an
unsigned integer (in big-endian format). This enhanced encoder is
then used by the `BorderAgent` when preparing MeshCoP TXT data and
also by the TREL module.
This commit is contained in:
Abtin Keshavarzian
2025-05-05 18:57:27 -07:00
committed by GitHub
parent c2316488e3
commit 19203d3287
5 changed files with 213 additions and 142 deletions
+53 -97
View File
@@ -181,13 +181,6 @@ void BorderAgent::SetServiceChangedCallback(ServiceChangedCallback aCallback, vo
PostServiceTask();
}
Error BorderAgent::PrepareServiceTxtData(ServiceTxtData &aTxtData) const
{
TxtEncoder encoder(GetInstance(), aTxtData);
return encoder.EncodeTxtData();
}
void BorderAgent::HandleNotifierEvents(Events aEvents)
{
if (aEvents.Contains(kEventThreadRoleChanged))
@@ -390,150 +383,113 @@ void BorderAgent::PostServiceTask(void)
}
}
//----------------------------------------------------------------------------------------------------------------------
// BorderAgent::TxtEncoder
Error BorderAgent::TxtEncoder::AppendTxtEntry(const char *aKey, const void *aValue, uint16_t aValueLength)
Error BorderAgent::PrepareServiceTxtData(ServiceTxtData &aTxtData)
{
Dns::TxtEntry txtEntry;
txtEntry.Init(aKey, reinterpret_cast<const uint8_t *>(aValue), aValueLength);
return txtEntry.AppendTo(mAppender);
}
template <> Error BorderAgent::TxtEncoder::AppendTxtEntry<NameData>(const char *aKey, const NameData &aObject)
{
return AppendTxtEntry(aKey, aObject.GetBuffer(), aObject.GetLength());
}
Error BorderAgent::TxtEncoder::EncodeTxtData(void)
{
Error error = kErrorNone;
Error error = kErrorNone;
Dns::TxtDataEncoder encoder(aTxtData.mData, sizeof(aTxtData.mData));
#if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE
{
Id id;
if (Get<BorderAgent>().GetId(id) == kErrorNone)
if (GetId(id) == kErrorNone)
{
SuccessOrExit(error = AppendTxtEntry("id", id));
SuccessOrExit(error = encoder.AppendEntry("id", id));
}
}
#endif
SuccessOrExit(error = AppendTxtEntry("nn", Get<NetworkNameManager>().GetNetworkName().GetAsData()));
SuccessOrExit(error = AppendTxtEntry("xp", Get<ExtendedPanIdManager>().GetExtPanId()));
SuccessOrExit(error = AppendTxtEntry("tv", NameData(kThreadVersionString, strlen(kThreadVersionString))));
SuccessOrExit(error = AppendTxtEntry("xa", Get<Mac::Mac>().GetExtAddress()));
SuccessOrExit(error = AppendTxtEntry("sb", BigEndian::HostSwap32(DetermineStateBitmap())));
SuccessOrExit(error = encoder.AppendNameEntry("nn", Get<NetworkNameManager>().GetNetworkName().GetAsData()));
SuccessOrExit(error = encoder.AppendEntry("xp", Get<ExtendedPanIdManager>().GetExtPanId()));
SuccessOrExit(error = encoder.AppendStringEntry("tv", kThreadVersionString));
SuccessOrExit(error = encoder.AppendEntry("xa", Get<Mac::Mac>().GetExtAddress()));
SuccessOrExit(error = encoder.AppendBigEndianUintEntry("sb", DetermineStateBitmap()));
if (Get<Mle::Mle>().IsAttached())
{
SuccessOrExit(
error = AppendTxtEntry("pt", BigEndian::HostSwap32(Get<Mle::Mle>().GetLeaderData().GetPartitionId())));
SuccessOrExit(error = encoder.AppendBigEndianUintEntry("pt", Get<Mle::Mle>().GetLeaderData().GetPartitionId()));
if (Get<MeshCoP::ActiveDatasetManager>().GetTimestamp().IsValid())
{
SuccessOrExit(error = AppendTxtEntry("at", Get<MeshCoP::ActiveDatasetManager>().GetTimestamp()));
SuccessOrExit(error = encoder.AppendEntry("at", Get<MeshCoP::ActiveDatasetManager>().GetTimestamp()));
}
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
SuccessOrExit(error = AppendBbrTxtEntry());
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
SuccessOrExit(error = AppendOmrTxtEntry());
#endif
mTxtData.mLength = mAppender.GetAppendedLength();
exit:
return error;
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
Error BorderAgent::TxtEncoder::AppendBbrTxtEntry(void)
{
Error error = kErrorNone;
const DomainName &domainName = Get<MeshCoP::NetworkNameManager>().GetDomainName();
if (Get<Mle::Mle>().IsAttached() && Get<BackboneRouter::Local>().IsEnabled())
{
BackboneRouter::Config bbrConfig;
Get<BackboneRouter::Local>().GetConfig(bbrConfig);
SuccessOrExit(error = AppendTxtEntry("sq", bbrConfig.mSequenceNumber));
SuccessOrExit(error = AppendTxtEntry("bb", BigEndian::HostSwap16(BackboneRouter::kBackboneUdpPort)));
SuccessOrExit(error = encoder.AppendEntry("sq", bbrConfig.mSequenceNumber));
SuccessOrExit(error = encoder.AppendBigEndianUintEntry("bb", BackboneRouter::kBackboneUdpPort));
}
error = AppendTxtEntry(
"dn", NameData(domainName.GetAsCString(), StringLength(domainName.GetAsCString(), sizeof(domainName))));
exit:
return error;
}
#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
Error BorderAgent::TxtEncoder::AppendOmrTxtEntry(void)
{
Error error = kErrorNone;
Ip6::Prefix prefix;
BorderRouter::RoutingManager::RoutePreference preference;
if (Get<BorderRouter::RoutingManager>().GetFavoredOmrPrefix(prefix, preference) == kErrorNone &&
prefix.GetLength() > 0)
{
uint8_t omrData[Ip6::NetworkPrefix::kSize + 1];
omrData[0] = prefix.GetLength();
memcpy(omrData + 1, prefix.GetBytes(), prefix.GetBytesSize());
SuccessOrExit(error = AppendTxtEntry("omr", omrData));
}
exit:
return error;
}
SuccessOrExit(error =
encoder.AppendNameEntry("dn", Get<MeshCoP::NetworkNameManager>().GetDomainName().GetAsData()));
#endif
uint32_t BorderAgent::TxtEncoder::DetermineStateBitmap(void)
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
{
Ip6::Prefix prefix;
BorderRouter::RoutingManager::RoutePreference preference;
if (Get<BorderRouter::RoutingManager>().GetFavoredOmrPrefix(prefix, preference) == kErrorNone &&
prefix.GetLength() > 0)
{
uint8_t omrData[Ip6::NetworkPrefix::kSize + 1];
omrData[0] = prefix.GetLength();
memcpy(omrData + 1, prefix.GetBytes(), prefix.GetBytesSize());
SuccessOrExit(error = encoder.AppendEntry("omr", omrData));
}
}
#endif
aTxtData.mLength = encoder.GetLength();
exit:
return error;
}
uint32_t BorderAgent::DetermineStateBitmap(void) const
{
uint32_t bitmap = 0;
bitmap |= (Get<BorderAgent>().IsRunning() ? kConnectionModePskc : kConnectionModeDisabled);
bitmap |= kAvailabilityHigh;
bitmap |= (IsRunning() ? StateBitmap::kConnectionModePskc : StateBitmap::kConnectionModeDisabled);
bitmap |= StateBitmap::kAvailabilityHigh;
switch (Get<Mle::Mle>().GetRole())
{
case Mle::DeviceRole::kRoleDisabled:
bitmap |= (kThreadIfStatusNotInitialized | kThreadRoleDisabledOrDetached);
bitmap |= (StateBitmap::kThreadIfStatusNotInitialized | StateBitmap::kThreadRoleDisabledOrDetached);
break;
case Mle::DeviceRole::kRoleDetached:
bitmap |= (kThreadIfStatusInitialized | kThreadRoleDisabledOrDetached);
bitmap |= (StateBitmap::kThreadIfStatusInitialized | StateBitmap::kThreadRoleDisabledOrDetached);
break;
case Mle::DeviceRole::kRoleChild:
bitmap |= (kThreadIfStatusActive | kThreadRoleChild);
bitmap |= (StateBitmap::kThreadIfStatusActive | StateBitmap::kThreadRoleChild);
break;
case Mle::DeviceRole::kRoleRouter:
bitmap |= (kThreadIfStatusActive | kThreadRoleRouter);
bitmap |= (StateBitmap::kThreadIfStatusActive | StateBitmap::kThreadRoleRouter);
break;
case Mle::DeviceRole::kRoleLeader:
bitmap |= (kThreadIfStatusActive | kThreadRoleLeader);
bitmap |= (StateBitmap::kThreadIfStatusActive | StateBitmap::kThreadRoleLeader);
break;
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
if (Get<Mle::Mle>().IsAttached())
{
bitmap |= (Get<BackboneRouter::Local>().IsEnabled() ? kFlagBbrIsActive : 0);
bitmap |= (Get<BackboneRouter::Local>().IsPrimary() ? kFlagBbrIsPrimary : 0);
bitmap |= (Get<BackboneRouter::Local>().IsEnabled() ? StateBitmap::kFlagBbrIsActive : 0);
bitmap |= (Get<BackboneRouter::Local>().IsPrimary() ? StateBitmap::kFlagBbrIsPrimary : 0);
}
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
if (Get<BorderAgent::EphemeralKeyManager>().GetState() != EphemeralKeyManager::kStateDisabled)
if (mEphemeralKeyManager.GetState() != EphemeralKeyManager::kStateDisabled)
{
bitmap |= kFlagEpskcSupported;
bitmap |= StateBitmap::kFlagEpskcSupported;
}
#endif
+4 -35
View File
@@ -227,7 +227,7 @@ public:
* @retval kErrorNone If successfully retrieved the Border Agent MeshCoP Service TXT data.
* @retval kErrorNoBufs If the buffer in @p aTxtData doesn't have enough size.
*/
Error PrepareServiceTxtData(ServiceTxtData &aTxtData) const;
Error PrepareServiceTxtData(ServiceTxtData &aTxtData);
#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
/**
@@ -471,19 +471,8 @@ private:
uint64_t mAllocationTime;
};
class TxtEncoder : public InstanceLocator
struct StateBitmap
{
public:
TxtEncoder(Instance &aInstance, ServiceTxtData &aTxtData)
: InstanceLocator(aInstance)
, mTxtData(aTxtData)
, mAppender(mTxtData.mData, sizeof(mTxtData.mData))
{
}
Error EncodeTxtData(void);
private:
// --- State Bitmap ConnectionMode ---
static constexpr uint8_t kOffsetConnectionMode = 0;
static constexpr uint32_t kMaskConnectionMode = 7 << kOffsetConnectionMode;
@@ -525,28 +514,6 @@ private:
// --- State Bitmap EpskcSupported ---
static constexpr uint8_t kOffsetEpskcSupported = 11;
static constexpr uint32_t kFlagEpskcSupported = 1 << kOffsetEpskcSupported;
uint32_t DetermineStateBitmap(void);
Error AppendTxtEntry(const char *aKey, const void *aValue, uint16_t aValueLength);
template <typename ObjectType> Error AppendTxtEntry(const char *aKey, const ObjectType &aObject)
{
static_assert(!TypeTraits::IsPointer<ObjectType>::kValue, "ObjectType must not be a pointer");
static_assert(!TypeTraits::IsSame<ObjectType, NameData>::kValue, "ObjectType must not be `NameData`");
return AppendTxtEntry(aKey, &aObject, sizeof(ObjectType));
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
Error AppendBbrTxtEntry(void);
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
Error AppendOmrTxtEntry(void);
#endif
ServiceTxtData &mTxtData;
Appender mAppender;
};
void UpdateState(void);
@@ -568,6 +535,8 @@ private:
static Coap::Message::Code CoapCodeFromError(Error aError);
uint32_t DetermineStateBitmap(void) const;
void PostServiceTask(void);
void HandleServiceTask(void);
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+18
View File
@@ -1390,6 +1390,24 @@ exit:
return error;
}
Error TxtDataEncoder::AppendBytesEntry(const char *aKey, const void *aBuffer, uint16_t aLength)
{
return TxtEntry(aKey, reinterpret_cast<const uint8_t *>(aBuffer), aLength).AppendTo(mAppender);
}
Error TxtDataEncoder::AppendStringEntry(const char *aKey, const char *aStringValue)
{
Error error;
uint16_t length = StringLength(aStringValue, kMaxStringEntryLength + 1);
VerifyOrExit(length <= kMaxStringEntryLength, error = kErrorInvalidArgs);
error = AppendBytesEntry(aKey, aStringValue, length);
exit:
return error;
}
bool AaaaRecord::IsValid(void) const
{
return GetType() == Dns::ResourceRecord::kTypeAaaa && GetSize() == sizeof(*this);
+133
View File
@@ -48,6 +48,7 @@
#include "common/message.hpp"
#include "common/owned_ptr.hpp"
#include "common/string.hpp"
#include "common/type_traits.hpp"
#include "crypto/ecdsa.hpp"
#include "net/ip4_types.hpp"
#include "net/ip6_address.hpp"
@@ -1258,6 +1259,138 @@ private:
static constexpr char kKeyValueSeparator = '=';
};
/**
* Represents a TXT data encoder.
*/
class TxtDataEncoder
{
public:
/**
* Maximum string length supported by `AppendStringEntry()`.
*/
static constexpr uint16_t kMaxStringEntryLength = 256;
/**
* Initializes the `TxtDataEncoder` to append to a `Message`.
*
* New TXT data entries are appended to the end of @p aMessage, growing its length.
*
* @param[in] aMessage The message to append to.
*/
explicit TxtDataEncoder(Message &aMessage)
: mAppender(aMessage)
{
}
/**
* Initializes the `TxtDataEncoder` to append in a given a buffer
*
* New TXT data entries are appended in the buffer starting from @p aBuffer up to is size @p aSize. The encoder
* does not allow content to be appended beyond the size of the buffer.
*
* @param[in] aBuffer A pointer to start of buffer.
* @param[in] aSize The maximum size of @p aBuffer (number of available bytes in buffer).
*/
TxtDataEncoder(uint8_t *aBuffer, uint16_t aSize)
: mAppender(aBuffer, aSize)
{
}
/**
* Returns the number of bytes in the TXT data container.
*
* When `TxtDataEncoder` uses a byte buffer container, this will return the number of encoded bytes in the buffer.
* When `TxtDataEncoder` uses a `Message`, this will return the message's current length, which includes any
* previously appended bytes in the message.
*
* @returns The number of bytes in the provided TXT data container.
*/
uint16_t GetLength(void) const { return mAppender.GetAppendedLength(); }
/**
* Appends a TXT entry for a given key and given value as a byte array.
*
* @param[in] aKey The TXT entry key string
* @param[in] aBuffer A pointer to buffer containing the TXT entry value.
* @param[in] aLength Number of bytes in @p aBuffer.
*
* @retval kErrorNone Successfully appended the TXT entry.
* @retval kErrorNoBufs Insufficient available buffers to append the entry.
*/
Error AppendBytesEntry(const char *aKey, const void *aBuffer, uint16_t aLength);
/**
* Appends a TXT entry for a given key and a given object as the entry's value.
*
* @tparam ObjectType The value object type.
* @param[in] aKey The TXT entry key string
* @param[in] aObject A reference to the value object.
*
* @retval kErrorNone Successfully appended the TXT entry.
* @retval kErrorNoBufs Insufficient available buffers to append the entry.
*/
template <typename ObjectType> Error AppendEntry(const char *aKey, const ObjectType &aObject)
{
static_assert(!TypeTraits::IsPointer<ObjectType>::kValue, "ObjectType must not be a pointer");
return AppendBytesEntry(aKey, &aObject, sizeof(ObjectType));
}
/**
* Appends a TXT entry for a given key and a given string for entry's value.
*
* The string length of @p aStringValues should not exceed `kMaxStringEntryLength`, otherwise `kErrorInvalidAgrs`
* is returned.
*
* @param[in] aKey The TXT entry key string
* @param[in] aStringValue The value string.
*
* @retval kErrorNone Successfully appended the TXT entry.
* @retval kErrorNoBufs Insufficient available buffers to append the entry.
* @retval kErrorInvalidArgs The @p aStringValue is too long.
*/
Error AppendStringEntry(const char *aKey, const char *aStringValue);
/**
* Appends a TXT entry for a given key and a given unsigned integral value using big-endian encoding.
*
* @tparam UintType The unsigned int type (`uint16_t` or `uint32_t`).
*
* @param[in] aKey The TXT entry key string
* @param[in] aUintValue The unsigned integer value.
*
* @retval kErrorNone Successfully appended the TXT entry.
* @retval kErrorNoBufs Insufficient available buffers to append the entry.
*/
template <typename UintType> Error AppendBigEndianUintEntry(const char *aKey, UintType aUintValue)
{
static_assert(TypeTraits::IsSame<UintType, uint8_t>::kValue || TypeTraits::IsSame<UintType, uint16_t>::kValue ||
TypeTraits::IsSame<UintType, uint32_t>::kValue ||
TypeTraits::IsSame<UintType, uint64_t>::kValue,
"UintType must be uint8/uint16/uint32/uint64");
return AppendEntry<UintType>(aKey, BigEndian::HostSwap<UintType>(aUintValue));
}
/**
* Appends a TXT entry for a given key and a given `NameData` as value.
*
* @param[in] aKey The TXT entry key string
* @param[in] aNameData The value as `NameData`
*
* @retval kErrorNone Successfully appended the TXT entry.
* @retval kErrorNoBufs Insufficient available buffers to append the entry.
*/
Error AppendNameEntry(const char *aKey, const MeshCoP::NameData &aNameData)
{
return AppendBytesEntry(aKey, aNameData.GetBuffer(), aNameData.GetLength());
}
private:
Appender mAppender;
};
/**
* Implements Resource Record (RR) body format.
*/
+5 -10
View File
@@ -149,24 +149,19 @@ void Interface::RegisterService(void)
/* ExtPanId */ sizeof(uint8_t) + sizeof(kTxtRecordExtPanIdKey) - 1 + sizeof(char) +
sizeof(MeshCoP::ExtendedPanId);
uint8_t txtDataBuffer[kTxtDataSize];
MutableData<kWithUint16Length> txtData;
Dns::TxtEntry txtEntries[2];
uint8_t txtData[kTxtDataSize];
Dns::TxtDataEncoder encoder(txtData, sizeof(txtData));
VerifyOrExit(mInitialized && mEnabled);
txtEntries[0].Init(kTxtRecordExtAddressKey, Get<Mac::Mac>().GetExtAddress().m8, sizeof(Mac::ExtAddress));
txtEntries[1].Init(kTxtRecordExtPanIdKey, Get<MeshCoP::ExtendedPanIdManager>().GetExtPanId().m8,
sizeof(MeshCoP::ExtendedPanId));
txtData.Init(txtDataBuffer, sizeof(txtDataBuffer));
SuccessOrAssert(Dns::TxtEntry::AppendEntries(txtEntries, GetArrayLength(txtEntries), txtData));
SuccessOrAssert(encoder.AppendEntry(kTxtRecordExtAddressKey, Get<Mac::Mac>().GetExtAddress()));
SuccessOrAssert(encoder.AppendEntry(kTxtRecordExtPanIdKey, Get<MeshCoP::ExtendedPanIdManager>().GetExtPanId()));
LogInfo("Registering DNS-SD service: port:%u, txt:\"%s=%s, %s=%s\"", mUdpPort, kTxtRecordExtAddressKey,
Get<Mac::Mac>().GetExtAddress().ToString().AsCString(), kTxtRecordExtPanIdKey,
Get<MeshCoP::ExtendedPanIdManager>().GetExtPanId().ToString().AsCString());
otPlatTrelRegisterService(&GetInstance(), mUdpPort, txtData.GetBytes(), static_cast<uint8_t>(txtData.GetLength()));
otPlatTrelRegisterService(&GetInstance(), mUdpPort, txtData, static_cast<uint8_t>(encoder.GetLength()));
exit:
return;