mirror of
https://github.com/espressif/openthread.git
synced 2026-07-27 14:27:47 +00:00
[dns-header] add common TXT record entry parsing API (#6157)
This commit adds a new public `ot` API for parsing an encoded DNS TXT record data into `TxtEntry` key/value pairs. It also updates the TXT data iterator model used for going through all TXT entries. The new iterator definition provides a small buffer to store the key string. This helps simplify the definition of `otDnsTxtEntry` to use null-terminated C string for the key. The new parsing APIs are used in CLI module. This commit also fixes an issue with `TxtEntry::AppendEntries()` using incorrect value for the key length. Finally, this commit adds a test covering the behavior of all `TxtEntry` related methods (encoding and parsing TXT data) in the unit test `test_dns`.
This commit is contained in:
@@ -37,6 +37,8 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <openthread/error.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -53,14 +55,11 @@ extern "C" {
|
||||
|
||||
#define OT_DNS_MAX_NAME_SIZE 255 ///< Maximum name string size (includes null char at the end of string).
|
||||
|
||||
#define OT_DNS_MAX_LABEL_SIZE 64 ///< Maximum label string size (include null char at the end of string)
|
||||
#define OT_DNS_MAX_LABEL_SIZE 64 ///< Maximum label string size (include null char at the end of string).
|
||||
|
||||
/**
|
||||
* Initializer for otDnsTxtIterator.
|
||||
*/
|
||||
#define OT_DNS_TXT_ITERATOR_INIT 0
|
||||
#define OT_DNS_TXT_KEY_MIN_LENGTH 1 ///< Minimum length of TXT record key string (RFC 6763 - section 6.4).
|
||||
|
||||
typedef uint16_t otDnsTxtIterator; ///< Used to iterate through the TXT entries.
|
||||
#define OT_DNS_TXT_KEY_MAX_LENGTH 9 ///< Recommended maximum length of TXT record key string (RFC 6763 - section 6.4).
|
||||
|
||||
/**
|
||||
* This structure represents a TXT record entry representing a key/value pair (RFC 6763 - section 6.3).
|
||||
@@ -75,9 +74,10 @@ typedef uint16_t otDnsTxtIterator; ///< Used to iterate through the TXT entries.
|
||||
typedef struct otDnsTxtEntry
|
||||
{
|
||||
/**
|
||||
* The TXT record key string. It doesn't need to be a null-terminated string and `mKeyLength` gives its length.
|
||||
* The TXT record key string.
|
||||
*
|
||||
* If `mKey` is not NULL, then the entry is treated as key/value pair with `mValue` buffer providing the value.
|
||||
* If `mKey` is not NULL, then it MUST be a null-terminated C string. The entry is treated as key/value pair with
|
||||
* `mValue` buffer providing the value.
|
||||
* - The entry is encoded as follows:
|
||||
* - A single string length byte followed by "key=value" format (without the quotation marks).
|
||||
- In this case, the overall encoded length must be 255 bytes or less.
|
||||
@@ -91,9 +91,55 @@ typedef struct otDnsTxtEntry
|
||||
const char * mKey;
|
||||
const uint8_t *mValue; ///< The TXT record value or already encoded TXT-DATA (depending on `mKey`).
|
||||
uint16_t mValueLength; ///< Number of bytes in `mValue` buffer.
|
||||
uint8_t mKeyLength; ///< Number of bytes in `mKey` buffer. MUST be set even if `mKey` is a null-terminated string.
|
||||
} otDnsTxtEntry;
|
||||
|
||||
/**
|
||||
* This structure represents an iterator for TXT record entires (key/value pairs).
|
||||
*
|
||||
* The data fields in this structure are intended for use by OpenThread core and caller should not read or change them.
|
||||
*
|
||||
*/
|
||||
typedef struct otDnsTxtEntryIterator
|
||||
{
|
||||
const void *mPtr;
|
||||
uint16_t mData[2];
|
||||
char mChar[OT_DNS_TXT_KEY_MAX_LENGTH + 1];
|
||||
} otDnsTxtEntryIterator;
|
||||
|
||||
/**
|
||||
* This function initializes a TXT record iterator.
|
||||
*
|
||||
* The buffer pointer @p aTxtData and its content MUST persist and remain unchanged while @p aIterator object
|
||||
* is being used.
|
||||
*
|
||||
* @param[in] aIterator A pointer to the iterator to initialize (MUST NOT be NULL).
|
||||
* @param[in] aTxtData A pointer to buffer containing the encoded TXT data.
|
||||
* @param[in] aTxtDataLength The length (number of bytes) of @p aTxtData.
|
||||
*
|
||||
*/
|
||||
void otDnsInitTxtEntryIterator(otDnsTxtEntryIterator *aIterator, const uint8_t *aTxtData, uint16_t aTxtDataLength);
|
||||
|
||||
/**
|
||||
* This function parses the TXT data from an iterator and gets the next TXT record entry (key/value pair).
|
||||
*
|
||||
* The @p aIterator MUST be initialized using `otDnsInitTxtEntryIterator()` before calling this function and the TXT
|
||||
* data buffer used to initialize the iterator MUST persist and remain unchanged. Otherwise the behavior of this
|
||||
* function is undefined.
|
||||
*
|
||||
* If the parsed key string length is smaller than or equal to `OT_DNS_TXT_KEY_MAX_LENGTH` (recommended max key length)
|
||||
* the key string is returned in `mKey` in @p aEntry. But if the key is longer, then `mKey` is set to NULL and the
|
||||
* entire encoded TXT entry string is returned in `mValue` and `mValueLength`.
|
||||
*
|
||||
* @param[in] aIterator A pointer to the iterator (MUST NOT be NULL).
|
||||
* @param[out] aEntry A pointer to a `otDnsTxtEntry` structure to output the parsed/read entry (MUST NOT be NULL).
|
||||
*
|
||||
* @retval OT_ERROR_NONE The next entry was parsed successfully. @p aEntry is updated.
|
||||
* @retval OT_ERROR_NOT_FOUND No more entries in the TXT data.
|
||||
* @retval OT_ERROR_PARSE The TXT data from @p aIterator is not well-formed.
|
||||
*
|
||||
*/
|
||||
otError otDnsGetNextTxtEntry(otDnsTxtEntryIterator *aIterator, otDnsTxtEntry *aEntry);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*
|
||||
|
||||
@@ -53,7 +53,7 @@ extern "C" {
|
||||
* @note This number versions both OpenThread platform and user APIs.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_API_VERSION (76)
|
||||
#define OPENTHREAD_API_VERSION (77)
|
||||
|
||||
/**
|
||||
* @addtogroup api-instance
|
||||
|
||||
@@ -304,20 +304,15 @@ uint16_t otSrpServerServiceGetWeight(const otSrpServerService *aService);
|
||||
uint16_t otSrpServerServiceGetPriority(const otSrpServerService *aService);
|
||||
|
||||
/**
|
||||
* This method returns the next TXT entry of the service instance.
|
||||
* This function returns the TXT record data of the service instance.
|
||||
*
|
||||
* @param[in] aService A pointer to the SRP service.
|
||||
* @param[inout] aIterator A pointer to the TXT iterator context. To get the first
|
||||
* TXT entry, it should be set to OT_DNS_TXT_ITERATOR_INIT.
|
||||
* @param[out] aTxtEntry A pointer to where the TXT entry will be placed.
|
||||
* @param[in] aService A pointer to the SRP service.
|
||||
* @param[out] aDataLength A pointer to return the TXT record data length. MUST NOT be NULL.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully found the next TXT entry.
|
||||
* @retval OT_ERROR_NOT_FOUND No subsequent TXT entry exists in the service.
|
||||
* @returns A pointer to the buffer containing the TXT record data (the TXT data length is returned in @p aDataLength).
|
||||
*
|
||||
*/
|
||||
otError otSrpServerServiceGetNextTxtEntry(const otSrpServerService *aService,
|
||||
otDnsTxtIterator * aIterator,
|
||||
otDnsTxtEntry * aTxtEntry);
|
||||
const uint8_t *otSrpServerServiceGetTxtData(const otSrpServerService *aService, uint16_t *aDataLength);
|
||||
|
||||
/**
|
||||
* This method returns the host which the service instance reside on.
|
||||
|
||||
+2
-2
@@ -817,12 +817,12 @@ inst1
|
||||
Port:1234, Priority:1, Weight:2, TTL:7200
|
||||
Host:host.example.com.
|
||||
HostAddress:fd00:0:0:0:0:0:0:abcd TTL:7200
|
||||
TXT-Data:(len:11) [096b65793d76616c756500] TTL:7300
|
||||
TXT:[a=6531, b=6c12] TTL:7300
|
||||
instance2
|
||||
Port:1234, Priority:1, Weight:2, TTL:7200
|
||||
Host:host.example.com.
|
||||
HostAddress:fd00:0:0:0:0:0:0:abcd TTL:7200
|
||||
TXT-Data:(len:11) [096b65793d76616c756500] TTL:7300
|
||||
TXT:[a=1234] TTL:7300
|
||||
Done
|
||||
```
|
||||
|
||||
|
||||
+49
-3
@@ -38,6 +38,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <openthread/diag.h>
|
||||
#include <openthread/dns.h>
|
||||
#include <openthread/icmp6.h>
|
||||
#include <openthread/link.h>
|
||||
#include <openthread/logging.h>
|
||||
@@ -1334,6 +1335,51 @@ exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
void Interpreter::OutputDnsTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength)
|
||||
{
|
||||
otDnsTxtEntry entry;
|
||||
otDnsTxtEntryIterator iterator;
|
||||
bool isFirst = true;
|
||||
|
||||
otDnsInitTxtEntryIterator(&iterator, aTxtData, aTxtDataLength);
|
||||
|
||||
OutputFormat("[");
|
||||
|
||||
while (otDnsGetNextTxtEntry(&iterator, &entry) == OT_ERROR_NONE)
|
||||
{
|
||||
if (!isFirst)
|
||||
{
|
||||
OutputFormat(", ");
|
||||
}
|
||||
|
||||
if (entry.mKey == nullptr)
|
||||
{
|
||||
// A null `mKey` indicates that the key in the entry is
|
||||
// longer than the recommended max key length, so the entry
|
||||
// could not be parsed. In this case, the whole entry is
|
||||
// returned encoded in `mValue`.
|
||||
|
||||
OutputFormat("[");
|
||||
OutputBytes(entry.mValue, entry.mValueLength);
|
||||
OutputFormat("]");
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputFormat("%s", entry.mKey);
|
||||
|
||||
if (entry.mValue != nullptr)
|
||||
{
|
||||
OutputFormat("=");
|
||||
OutputBytes(entry.mValue, entry.mValueLength);
|
||||
}
|
||||
}
|
||||
|
||||
isFirst = false;
|
||||
}
|
||||
|
||||
OutputFormat("]");
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
|
||||
|
||||
otError Interpreter::GetDnsConfig(uint8_t aArgsLength,
|
||||
@@ -1473,9 +1519,9 @@ void Interpreter::OutputDnsServiceInfo(uint8_t aIndentSize, const otDnsServiceIn
|
||||
OutputFormat(aIndentSize, "HostAddress:");
|
||||
OutputIp6Address(aServiceInfo.mHostAddress);
|
||||
OutputLine(" TTL:%u", aServiceInfo.mHostAddressTtl);
|
||||
OutputFormat(aIndentSize, "TXT-Data:(len:%d) [", aServiceInfo.mTxtDataSize);
|
||||
OutputBytes(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize);
|
||||
OutputFormat("] TTL:%u", aServiceInfo.mTxtDataTtl);
|
||||
OutputFormat(aIndentSize, "TXT:");
|
||||
OutputDnsTxtData(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize);
|
||||
OutputLine(" TTL:%u", aServiceInfo.mTxtDataTtl);
|
||||
}
|
||||
|
||||
void Interpreter::HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse, void *aContext)
|
||||
|
||||
@@ -557,6 +557,8 @@ private:
|
||||
void OutputChildTableEntry(uint8_t aIndentSize, const otNetworkDiagChildEntry &aChildEntry);
|
||||
#endif
|
||||
|
||||
void OutputDnsTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength);
|
||||
|
||||
#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
|
||||
otError GetDnsConfig(uint8_t aArgsLength, char *aArgs[], otDnsQueryConfig *&aConfig, uint8_t aStartArgsIndex);
|
||||
static void HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext);
|
||||
|
||||
@@ -390,8 +390,7 @@ otError SrpClient::ProcessService(uint8_t aArgsLength, char *aArgs[])
|
||||
{
|
||||
entry->mService.mNumTxtEntries = 1;
|
||||
entry->mService.mTxtEntries = &entry->mTxtEntry;
|
||||
entry->mTxtEntry.mKey = nullptr; // Treat`mValue` as an already encoded TXT-DATA
|
||||
entry->mTxtEntry.mKeyLength = 0;
|
||||
entry->mTxtEntry.mKey = nullptr; // Treat `mValue` as an already encoded TXT-DATA
|
||||
entry->mTxtEntry.mValue = entry->mTxtBuffer;
|
||||
entry->mTxtEntry.mValueLength = sizeof(entry->mTxtBuffer);
|
||||
|
||||
|
||||
@@ -163,33 +163,6 @@ exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
void SrpServer::OutputServiceTxtEntries(const otSrpServerService *aService)
|
||||
{
|
||||
uint16_t count = 0;
|
||||
otDnsTxtEntry entry;
|
||||
otDnsTxtIterator iterator = OT_DNS_TXT_ITERATOR_INIT;
|
||||
|
||||
mInterpreter.OutputFormat("[");
|
||||
|
||||
while (otSrpServerServiceGetNextTxtEntry(aService, &iterator, &entry) == OT_ERROR_NONE)
|
||||
{
|
||||
if (count != 0)
|
||||
{
|
||||
mInterpreter.OutputFormat(", ");
|
||||
}
|
||||
|
||||
mInterpreter.Output(entry.mKey, entry.mKeyLength);
|
||||
if (entry.mValue != nullptr)
|
||||
{
|
||||
mInterpreter.OutputFormat("=");
|
||||
mInterpreter.OutputBytes(entry.mValue, entry.mValueLength);
|
||||
}
|
||||
++count;
|
||||
}
|
||||
|
||||
mInterpreter.OutputFormat("]");
|
||||
}
|
||||
|
||||
void SrpServer::OutputHostAddresses(const otSrpServerHost *aHost)
|
||||
{
|
||||
const otIp6Address *addresses;
|
||||
@@ -226,7 +199,9 @@ otError SrpServer::ProcessService(uint8_t aArgsLength, char *aArgs[])
|
||||
|
||||
while ((service = otSrpServerHostGetNextService(host, service)) != nullptr)
|
||||
{
|
||||
bool isDeleted = otSrpServerServiceIsDeleted(service);
|
||||
bool isDeleted = otSrpServerServiceIsDeleted(service);
|
||||
const uint8_t *txtData;
|
||||
uint16_t txtDataLength;
|
||||
|
||||
mInterpreter.OutputLine("%s", otSrpServerServiceGetFullName(service));
|
||||
mInterpreter.OutputLine(Interpreter::kIndentSize, "deleted: %s", isDeleted ? "true" : "false");
|
||||
@@ -239,17 +214,16 @@ otError SrpServer::ProcessService(uint8_t aArgsLength, char *aArgs[])
|
||||
mInterpreter.OutputLine(Interpreter::kIndentSize, "priority: %hu", otSrpServerServiceGetPriority(service));
|
||||
mInterpreter.OutputLine(Interpreter::kIndentSize, "weight: %hu", otSrpServerServiceGetWeight(service));
|
||||
|
||||
mInterpreter.OutputSpaces(Interpreter::kIndentSize);
|
||||
mInterpreter.OutputFormat("TXT: ");
|
||||
OutputServiceTxtEntries(service);
|
||||
mInterpreter.OutputFormat("\r\n");
|
||||
txtData = otSrpServerServiceGetTxtData(service, &txtDataLength);
|
||||
mInterpreter.OutputFormat(Interpreter::kIndentSize, "TXT: ");
|
||||
mInterpreter.OutputDnsTxtData(txtData, txtDataLength);
|
||||
mInterpreter.OutputLine("");
|
||||
|
||||
mInterpreter.OutputLine(Interpreter::kIndentSize, "host: %s", otSrpServerHostGetFullName(host));
|
||||
|
||||
mInterpreter.OutputSpaces(Interpreter::kIndentSize);
|
||||
mInterpreter.OutputFormat("addresses: ");
|
||||
mInterpreter.OutputFormat(Interpreter::kIndentSize, "addresses: ");
|
||||
OutputHostAddresses(host);
|
||||
mInterpreter.OutputFormat("\r\n");
|
||||
mInterpreter.OutputLine("");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,6 @@ private:
|
||||
otError ProcessService(uint8_t aArgsLength, char *aArgs[]);
|
||||
otError ProcessHelp(uint8_t aArgsLength, char *aArgs[]);
|
||||
|
||||
void OutputServiceTxtEntries(const otSrpServerService *aService);
|
||||
void OutputHostAddresses(const otSrpServerHost *aHost);
|
||||
|
||||
static constexpr Command sCommands[] = {
|
||||
|
||||
@@ -38,9 +38,20 @@
|
||||
#include "common/instance.hpp"
|
||||
#include "common/locator-getters.hpp"
|
||||
#include "net/dns_client.hpp"
|
||||
#include "net/dns_headers.hpp"
|
||||
|
||||
using namespace ot;
|
||||
|
||||
void otDnsInitTxtEntryIterator(otDnsTxtEntryIterator *aIterator, const uint8_t *aTxtData, uint16_t aTxtDataLength)
|
||||
{
|
||||
static_cast<Dns::TxtEntry::Iterator *>(aIterator)->Init(aTxtData, aTxtDataLength);
|
||||
}
|
||||
|
||||
otError otDnsGetNextTxtEntry(otDnsTxtEntryIterator *aIterator, otDnsTxtEntry *aEntry)
|
||||
{
|
||||
return static_cast<Dns::TxtEntry::Iterator *>(aIterator)->GetNextEntry(*static_cast<Dns::TxtEntry *>(aEntry));
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
|
||||
|
||||
const otDnsQueryConfig *otDnsClientGetDefaultConfig(otInstance *aInstance)
|
||||
|
||||
@@ -147,12 +147,13 @@ uint16_t otSrpServerServiceGetPriority(const otSrpServerService *aService)
|
||||
return static_cast<const Srp::Server::Service *>(aService)->GetPriority();
|
||||
}
|
||||
|
||||
otError otSrpServerServiceGetNextTxtEntry(const otSrpServerService *aService,
|
||||
otDnsTxtIterator * aIterator,
|
||||
otDnsTxtEntry * aTxtEntry)
|
||||
const uint8_t *otSrpServerServiceGetTxtData(const otSrpServerService *aService, uint16_t *aDataLength)
|
||||
{
|
||||
return static_cast<const Srp::Server::Service *>(aService)->GetNextTxtEntry(
|
||||
*aIterator, static_cast<Dns::TxtEntry &>(*aTxtEntry));
|
||||
const Srp::Server::Service &service = *static_cast<const Srp::Server::Service *>(aService);
|
||||
|
||||
*aDataLength = service.GetTxtDataLength();
|
||||
|
||||
return service.GetTxtData();
|
||||
}
|
||||
|
||||
const otSrpServerHost *otSrpServerServiceGetHost(const otSrpServerService *aService)
|
||||
|
||||
@@ -798,38 +798,114 @@ exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
void TxtEntry::Iterator::Init(const uint8_t *aTxtData, uint16_t aTxtDataLength)
|
||||
{
|
||||
SetTxtData(aTxtData);
|
||||
SetTxtDataLength(aTxtDataLength);
|
||||
SetTxtDataPosition(0);
|
||||
}
|
||||
|
||||
otError TxtEntry::Iterator::GetNextEntry(TxtEntry &aEntry)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
uint8_t length;
|
||||
uint8_t index;
|
||||
const char *cur;
|
||||
char * keyBuffer = GetKeyBuffer();
|
||||
|
||||
static_assert(sizeof(mChar) == TxtEntry::kMaxKeyLength + 1, "KeyBuffer cannot fit the max key length");
|
||||
|
||||
VerifyOrExit(GetTxtData() != nullptr, error = OT_ERROR_PARSE);
|
||||
|
||||
aEntry.mKey = keyBuffer;
|
||||
|
||||
while ((cur = GetTxtData() + GetTxtDataPosition()) < GetTxtDataEnd())
|
||||
{
|
||||
length = static_cast<uint8_t>(*cur);
|
||||
|
||||
cur++;
|
||||
VerifyOrExit(cur + length <= GetTxtDataEnd(), error = OT_ERROR_PARSE);
|
||||
IncreaseTxtDataPosition(sizeof(uint8_t) + length);
|
||||
|
||||
// Silently skip over an empty string or if the string starts with
|
||||
// a `=` character (i.e., missing key) - RFC 6763 - section 6.4.
|
||||
|
||||
if ((length == 0) || (cur[0] == kKeyValueSeparator))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (index = 0; index < length; index++)
|
||||
{
|
||||
if (cur[index] == kKeyValueSeparator)
|
||||
{
|
||||
keyBuffer[index++] = kNullChar; // Increment index to skip over `=`.
|
||||
aEntry.mValue = reinterpret_cast<const uint8_t *>(&cur[index]);
|
||||
aEntry.mValueLength = length - index;
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
if (index >= kMaxKeyLength)
|
||||
{
|
||||
// The key is larger than recommended max key length.
|
||||
// In this case, we return the full encoded string in
|
||||
// `mValue` and `mValueLength` and set `mKey` to
|
||||
// `nullptr`.
|
||||
|
||||
aEntry.mKey = nullptr;
|
||||
aEntry.mValue = reinterpret_cast<const uint8_t *>(cur);
|
||||
aEntry.mValueLength = length;
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
keyBuffer[index] = cur[index];
|
||||
}
|
||||
|
||||
// If we reach the end of the string without finding `=` then
|
||||
// it is a boolean key attribute (encoded as "key").
|
||||
|
||||
keyBuffer[index] = kNullChar;
|
||||
aEntry.mValue = nullptr;
|
||||
aEntry.mValueLength = 0;
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
error = OT_ERROR_NOT_FOUND;
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError TxtEntry::AppendTo(Message &aMessage) const
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
uint8_t length;
|
||||
otError error = OT_ERROR_NONE;
|
||||
uint16_t keyLength;
|
||||
|
||||
if (mKey == nullptr)
|
||||
{
|
||||
VerifyOrExit(mValue != nullptr);
|
||||
VerifyOrExit((mValue != nullptr) && (mValueLength != 0));
|
||||
error = aMessage.AppendBytes(mValue, mValueLength);
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
length = mKeyLength;
|
||||
keyLength = StringLength(mKey, static_cast<uint16_t>(kMaxKeyValueEncodedSize) + 1);
|
||||
|
||||
VerifyOrExit(length <= kMaxKeyLength, error = OT_ERROR_INVALID_ARGS);
|
||||
VerifyOrExit(kMinKeyLength <= keyLength, error = OT_ERROR_INVALID_ARGS);
|
||||
|
||||
if (mValue == nullptr)
|
||||
{
|
||||
// Treat as a boolean attribute and encoded as "key" (with no `=`).
|
||||
SuccessOrExit(error = aMessage.Append(length));
|
||||
error = aMessage.AppendBytes(mKey, length);
|
||||
SuccessOrExit(error = aMessage.Append<uint8_t>(static_cast<uint8_t>(keyLength)));
|
||||
error = aMessage.AppendBytes(mKey, keyLength);
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
// Treat as key/value and encode as "key=value", value may be empty.
|
||||
|
||||
VerifyOrExit(mValueLength + length + sizeof(char) <= kMaxKeyValueEncodedSize, error = OT_ERROR_INVALID_ARGS);
|
||||
VerifyOrExit(mValueLength + keyLength + sizeof(char) <= kMaxKeyValueEncodedSize, error = OT_ERROR_INVALID_ARGS);
|
||||
|
||||
length += static_cast<uint8_t>(mValueLength + sizeof(char));
|
||||
|
||||
SuccessOrExit(error = aMessage.Append(length));
|
||||
SuccessOrExit(error = aMessage.AppendBytes(mKey, length));
|
||||
SuccessOrExit(error = aMessage.Append<uint8_t>(static_cast<uint8_t>(keyLength + mValueLength + sizeof(char))));
|
||||
SuccessOrExit(error = aMessage.AppendBytes(mKey, keyLength));
|
||||
SuccessOrExit(error = aMessage.Append<char>(kKeyValueSeparator));
|
||||
error = aMessage.AppendBytes(mValue, mValueLength);
|
||||
|
||||
@@ -962,51 +1038,5 @@ exit:
|
||||
return valid;
|
||||
}
|
||||
|
||||
otError TxtRecord::GetNextTxtEntry(const uint8_t *aTxtData,
|
||||
uint16_t aTxtLength,
|
||||
TxtIterator & aIterator,
|
||||
TxtEntry & aTxtEntry)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
for (uint16_t i = aIterator; i < aTxtLength;)
|
||||
{
|
||||
uint8_t length = aTxtData[i++];
|
||||
|
||||
OT_ASSERT(i + length <= aTxtLength);
|
||||
aTxtEntry.mKey = reinterpret_cast<const char *>(aTxtData + i);
|
||||
aTxtEntry.mKeyLength = length;
|
||||
aTxtEntry.mValue = nullptr;
|
||||
aTxtEntry.mValueLength = 0;
|
||||
|
||||
for (uint8_t j = 0; j < length; ++j)
|
||||
{
|
||||
if (aTxtData[i + j] == TxtEntry::kKeyValueSeparator)
|
||||
{
|
||||
aTxtEntry.mKeyLength = j;
|
||||
aTxtEntry.mValue = aTxtData + i + j + 1;
|
||||
aTxtEntry.mValueLength = length - j - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
i += length;
|
||||
|
||||
// Per RFC 6763, a TXT entry with empty key MUST be silently ignored.
|
||||
if (aTxtEntry.mKeyLength == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
aIterator = i;
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
error = OT_ERROR_NOT_FOUND;
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
} // namespace Dns
|
||||
} // namespace ot
|
||||
|
||||
@@ -1051,6 +1051,91 @@ class TxtEntry : public otDnsTxtEntry
|
||||
friend class TxtRecord;
|
||||
|
||||
public:
|
||||
enum : uint8_t
|
||||
{
|
||||
kMinKeyLength = OT_DNS_TXT_KEY_MIN_LENGTH, ///< Minimum length of key string (RFC 6763 - section 6.4).
|
||||
kMaxKeyLength = OT_DNS_TXT_KEY_MAX_LENGTH, ///< Recommended max length of key string (RFC 6763 - section 6.4).
|
||||
};
|
||||
|
||||
/**
|
||||
* This class represents an iterator for TXT record entires (key/value pairs).
|
||||
*
|
||||
*/
|
||||
class Iterator : public otDnsTxtEntryIterator
|
||||
{
|
||||
friend class TxtEntry;
|
||||
|
||||
public:
|
||||
/**
|
||||
* This method initializes a TXT record iterator.
|
||||
*
|
||||
* The buffer pointer @p aTxtData and its content MUST persist and remain unchanged while the iterator object
|
||||
* is being used.
|
||||
*
|
||||
* @param[in] aTxtData A pointer to buffer containing the encoded TXT data.
|
||||
* @param[in] aTxtDataLength The length (number of bytes) of @p aTxtData.
|
||||
*
|
||||
*/
|
||||
void Init(const uint8_t *aTxtData, uint16_t aTxtDataLength);
|
||||
|
||||
/**
|
||||
* This method parses the TXT data from the `Iterator` and gets the next TXT record entry (key/value pair).
|
||||
*
|
||||
* The `Iterator` instance MUST be initialized using `Init()` before calling this method and the TXT data
|
||||
* buffer used to initialize the iterator MUST persist and remain unchanged.
|
||||
*
|
||||
* If the parsed key string length is smaller than or equal to `kMaxKeyLength` (recommended max key length)
|
||||
* the key string is returned in `mKey` in @p aEntry. But if the key is longer, then `mKey` is set to NULL and
|
||||
* the entire encoded TXT entry is returned in `mValue` and `mValueLength`.
|
||||
*
|
||||
* @param[out] aEntry A reference to a `TxtEntry` to output the parsed/read entry.
|
||||
*
|
||||
* @retval OT_ERROR_NONE The next entry was parsed successfully. @p aEntry is updated.
|
||||
* @retval OT_ERROR_NOT_FOUND No more entries in TXT data.
|
||||
* @retval OT_ERROR_PARSE The TXT data from `Iterator` is not well-formed.
|
||||
*
|
||||
*/
|
||||
otError GetNextEntry(TxtEntry &aEntry);
|
||||
|
||||
private:
|
||||
enum : uint8_t
|
||||
{
|
||||
kIndexTxtLength = 0,
|
||||
kIndexTxtPosition = 1,
|
||||
};
|
||||
|
||||
const char *GetTxtData(void) const { return reinterpret_cast<const char *>(mPtr); }
|
||||
void SetTxtData(const uint8_t *aTxtData) { mPtr = aTxtData; }
|
||||
uint16_t GetTxtDataLength(void) const { return mData[kIndexTxtLength]; }
|
||||
void SetTxtDataLength(uint16_t aLength) { mData[kIndexTxtLength] = aLength; }
|
||||
uint16_t GetTxtDataPosition(void) const { return mData[kIndexTxtPosition]; }
|
||||
void SetTxtDataPosition(uint16_t aValue) { mData[kIndexTxtPosition] = aValue; }
|
||||
void IncreaseTxtDataPosition(uint16_t aIncrement) { mData[kIndexTxtPosition] += aIncrement; }
|
||||
char * GetKeyBuffer(void) { return mChar; }
|
||||
const char *GetTxtDataEnd(void) const { return GetTxtData() + GetTxtDataLength(); }
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the default constructor for a `TxtEntry` object.
|
||||
*
|
||||
*/
|
||||
TxtEntry(void) = default;
|
||||
|
||||
/**
|
||||
* This constructor initializes a `TxtEntry` object.
|
||||
*
|
||||
* @param[in] aKey A pointer to the key string.
|
||||
* @param[in] aValue A pointer to a buffer containing the value.
|
||||
* @param[in] aValueLength Number of bytes in @p aValue buffer.
|
||||
*
|
||||
*/
|
||||
TxtEntry(const char *aKey, const uint8_t *aValue, uint8_t aValueLength)
|
||||
{
|
||||
mKey = aKey;
|
||||
mValue = aValue;
|
||||
mValueLength = aValueLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method encodes and appends the `TxtEntry` to a message.
|
||||
*
|
||||
@@ -1070,7 +1155,6 @@ public:
|
||||
* @param[in] aNumEntries The number of entries in @p aEntries array.
|
||||
* @param[in] aMessage The message to append to.
|
||||
*
|
||||
*
|
||||
* @retval OT_ERROR_NONE Entries appended successfully to @p aMessage.
|
||||
* @retval OT_ERROR_INVALID_ARGS The `TxTEntry` info is not valid.
|
||||
* @retval OT_ERROR_NO_BUFS Insufficient available buffers to grow the message.
|
||||
@@ -1079,16 +1163,15 @@ public:
|
||||
static otError AppendEntries(const TxtEntry *aEntries, uint8_t aNumEntries, Message &aMessage);
|
||||
|
||||
private:
|
||||
enum : uint8_t
|
||||
{
|
||||
kMaxKeyValueEncodedSize = 255,
|
||||
};
|
||||
|
||||
enum : char
|
||||
{
|
||||
kKeyValueSeparator = '=',
|
||||
};
|
||||
|
||||
enum : uint8_t
|
||||
{
|
||||
kMinKeyLength = 1,
|
||||
kMaxKeyLength = 9,
|
||||
kMaxKeyValueEncodedSize = 255,
|
||||
kNullChar = '\0',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1553,8 +1636,6 @@ public:
|
||||
kType = kTypeTxt, ///< The TXT record type.
|
||||
};
|
||||
|
||||
typedef otDnsTxtIterator TxtIterator;
|
||||
|
||||
/**
|
||||
* This method initializes the TXT Resource Record by setting its type and class.
|
||||
*
|
||||
@@ -1600,26 +1681,6 @@ public:
|
||||
*/
|
||||
static bool VerifyTxtData(const uint8_t *aTxtData, uint16_t aTxtLength);
|
||||
|
||||
/**
|
||||
* This static method returns the next TXT entry in the encoded TXT data buffer.
|
||||
*
|
||||
* This method assumes that @p aTxtData has already been verified by `VerifyTxtData()`.
|
||||
*
|
||||
* @param[in] aTxtData The encoded TXT data buffer.
|
||||
* @param[in] aTxtLength The length of the encoded TXT data.
|
||||
* @param[inout] aIterator A reference to the TXT iterator context. To get the first
|
||||
* TXT entry, it should be set to OT_DNS_TXT_ITERATOR_INIT.
|
||||
* @param[out] aTxtEntry A reference to where the TXT entry will be placed.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully found the next TXT entry.
|
||||
* @retval OT_ERROR_NOT_FOUND No subsequent TXT entry exists in the service.
|
||||
*
|
||||
*/
|
||||
static otError GetNextTxtEntry(const uint8_t *aTxtData,
|
||||
uint16_t aTxtLength,
|
||||
TxtIterator & aIterator,
|
||||
TxtEntry & aTxtEntry);
|
||||
|
||||
} OT_TOOL_PACKED_END;
|
||||
|
||||
/**
|
||||
|
||||
@@ -675,28 +675,16 @@ otError Server::AppendTxtRecord(Message & aMessage,
|
||||
uint32_t aTtl,
|
||||
NameCompressInfo & aCompressInfo)
|
||||
{
|
||||
TxtRecord txtRecord;
|
||||
otError error;
|
||||
Dns::TxtRecord::TxtIterator txtIterator = OT_DNS_TXT_ITERATOR_INIT;
|
||||
Dns::TxtEntry txtEntry;
|
||||
uint16_t recordOffset;
|
||||
bool hasAnyTxtEntry = false;
|
||||
otError error;
|
||||
uint16_t recordOffset;
|
||||
TxtRecord txtRecord;
|
||||
|
||||
SuccessOrExit(error = AppendInstanceName(aMessage, aInstanceName, aCompressInfo));
|
||||
|
||||
recordOffset = aMessage.GetLength();
|
||||
SuccessOrExit(error = aMessage.SetLength(recordOffset + sizeof(txtRecord)));
|
||||
|
||||
while (aService.GetNextTxtEntry(txtIterator, txtEntry) == OT_ERROR_NONE)
|
||||
{
|
||||
SuccessOrExit(error = txtEntry.AppendTo(aMessage));
|
||||
hasAnyTxtEntry = true;
|
||||
}
|
||||
|
||||
if (!hasAnyTxtEntry)
|
||||
{
|
||||
SuccessOrExit(error = aMessage.Append<uint8_t>(0));
|
||||
}
|
||||
SuccessOrExit(error = aMessage.AppendBytes(aService.GetTxtData(), aService.GetTxtDataLength()));
|
||||
|
||||
txtRecord.Init();
|
||||
txtRecord.SetTtl(aTtl);
|
||||
|
||||
@@ -1317,11 +1317,6 @@ exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError Server::Service::GetNextTxtEntry(Dns::TxtRecord::TxtIterator &aIterator, Dns::TxtEntry &aTxtEntry) const
|
||||
{
|
||||
return Dns::TxtRecord::GetNextTxtEntry(mTxtData, mTxtLength, aIterator, aTxtEntry);
|
||||
}
|
||||
|
||||
TimeMilli Server::Service::GetExpireTime(void) const
|
||||
{
|
||||
OT_ASSERT(!mIsDeleted);
|
||||
|
||||
@@ -150,17 +150,20 @@ public:
|
||||
uint16_t GetPriority(void) const { return mPriority; }
|
||||
|
||||
/**
|
||||
* This method returns the next TXT entry of the service instance.
|
||||
* This method returns the TXT record data of the service instance.
|
||||
*
|
||||
* @param[inout] aIterator A pointer to the TXT iterator context. To get the first
|
||||
* TXT entry, it should be set to OT_DNS_TXT_ITERATOR_INIT.
|
||||
* @param[out] aTxtEntry A pointer to where the TXT entry will be placed.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully found the next TXT entry.
|
||||
* @retval OT_ERROR_NOT_FOUND No subsequent TXT entry exists in the service.
|
||||
* @returns A pointer to the buffer containing the TXT record data.
|
||||
*
|
||||
*/
|
||||
otError GetNextTxtEntry(Dns::TxtRecord::TxtIterator &aIterator, Dns::TxtEntry &aTxtEntry) const;
|
||||
const uint8_t *GetTxtData(void) const { return mTxtData; }
|
||||
|
||||
/**
|
||||
* This method returns the TXT recored data length of the service instance.
|
||||
*
|
||||
* @return The TXT record data length (number of bytes in buffer returned from `GetTxtData()`).
|
||||
*
|
||||
*/
|
||||
uint16_t GetTxtDataLength(void) const { return mTxtLength; }
|
||||
|
||||
/**
|
||||
* This method returns the host which the service instance reside on.
|
||||
|
||||
@@ -2460,15 +2460,15 @@ class NodeImpl:
|
||||
def dns_resolve_service(self, instance, service, server=None, port=53):
|
||||
"""
|
||||
Resolves the service instance and returns the instance information as a dict.
|
||||
|
||||
Example return value:
|
||||
|
||||
Example return value:
|
||||
{
|
||||
'port': 12345,
|
||||
'priority': 0,
|
||||
'weight': 0,
|
||||
'host': 'ins1._ipps._tcp.default.service.arpa.',
|
||||
'address': '2001::1',
|
||||
'txt_data': b'\x00',
|
||||
'txt_data': 'a=00, b=02bb',
|
||||
'srv_ttl': 7100,
|
||||
'txt_ttl': 7100,
|
||||
'aaaa_ttl': 7100,
|
||||
@@ -2487,11 +2487,11 @@ class NodeImpl:
|
||||
# Port:22222, Priority:2, Weight:2, TTL:7155
|
||||
# Host:host2.default.service.arpa.
|
||||
# HostAddress:0:0:0:0:0:0:0:0 TTL:0
|
||||
# TXT-Data:(len:1) [00] TTL:7155
|
||||
# TXT:[a=00, b=02bb] TTL:7155
|
||||
# Done
|
||||
|
||||
m = re.match(
|
||||
r'.*Port:(\d+), Priority:(\d+), Weight:(\d+), TTL:(\d+)\s+Host:(.*?)\s+HostAddress:(\S+) TTL:(\d+)\s+TXT-Data:\(len:\d+\) \[(.*?)\] TTL:(\d+)',
|
||||
r'.*Port:(\d+), Priority:(\d+), Weight:(\d+), TTL:(\d+)\s+Host:(.*?)\s+HostAddress:(\S+) TTL:(\d+)\s+TXT:\[(.*?)\] TTL:(\d+)',
|
||||
'\r'.join(output))
|
||||
if m:
|
||||
port, priority, weight, srv_ttl, hostname, address, aaaa_ttl, txt_data, txt_ttl = m.groups()
|
||||
@@ -2501,7 +2501,7 @@ class NodeImpl:
|
||||
'weight': int(weight),
|
||||
'host': hostname,
|
||||
'address': address,
|
||||
'txt_data': self.__parse_hex_string(txt_data),
|
||||
'txt_data': txt_data,
|
||||
'srv_ttl': int(srv_ttl),
|
||||
'txt_ttl': int(txt_ttl),
|
||||
'aaaa_ttl': int(aaaa_ttl),
|
||||
@@ -2526,7 +2526,7 @@ class NodeImpl:
|
||||
'weight': 1,
|
||||
'host': 'ins1._ipps._tcp.default.service.arpa.',
|
||||
'address': '2001::1',
|
||||
'txt_data': b'\x00',
|
||||
'txt_data': 'a=00, b=11cf',
|
||||
'srv_ttl': 7100,
|
||||
'txt_ttl': 7100,
|
||||
'aaaa_ttl': 7100,
|
||||
@@ -2537,7 +2537,7 @@ class NodeImpl:
|
||||
'weight': 2,
|
||||
'host': 'ins2._ipps._tcp.default.service.arpa.',
|
||||
'address': '2001::2',
|
||||
'txt_data': b'\x00',
|
||||
'txt_data': 'a=01, b=23dd',
|
||||
'srv_ttl': 7100,
|
||||
'txt_ttl': 7100,
|
||||
'aaaa_ttl': 7100,
|
||||
@@ -2557,17 +2557,17 @@ class NodeImpl:
|
||||
# Port:22222, Priority:2, Weight:2, TTL:7175
|
||||
# Host:host2.default.service.arpa.
|
||||
# HostAddress:fd00:db8:0:0:3205:28dd:5b87:6a63 TTL:7175
|
||||
# TXT-Data:(len:1) [00] TTL:7175
|
||||
# TXT:[a=00, b=11cf] TTL:7175
|
||||
# ins1
|
||||
# Port:11111, Priority:1, Weight:1, TTL:7170
|
||||
# Host:host1.default.service.arpa.
|
||||
# HostAddress:fd00:db8:0:0:39f4:d9:eb4f:778 TTL:7170
|
||||
# TXT-Data:(len:1) [00] TTL:7170
|
||||
# TXT:[a=01, b=23dd] TTL:7170
|
||||
# Done
|
||||
|
||||
result = {}
|
||||
for ins, port, priority, weight, srv_ttl, hostname, address, aaaa_ttl, txt_data, txt_ttl in re.findall(
|
||||
r'(.*?)\s+Port:(\d+), Priority:(\d+), Weight:(\d+), TTL:(\d+)\s*Host:(\S+)\s+HostAddress:(\S+) TTL:(\d+)\s+TXT-Data:\(len:\d+\) \[(.*?)\] TTL:(\d+)',
|
||||
r'(.*?)\s+Port:(\d+), Priority:(\d+), Weight:(\d+), TTL:(\d+)\s*Host:(\S+)\s+HostAddress:(\S+) TTL:(\d+)\s+TXT:\[(.*?)\] TTL:(\d+)',
|
||||
output):
|
||||
result[ins] = {
|
||||
'port': int(port),
|
||||
@@ -2575,7 +2575,7 @@ class NodeImpl:
|
||||
'weight': int(weight),
|
||||
'host': hostname,
|
||||
'address': address,
|
||||
'txt_data': self.__parse_hex_string(txt_data),
|
||||
'txt_data': txt_data,
|
||||
'srv_ttl': int(srv_ttl),
|
||||
'txt_ttl': int(txt_ttl),
|
||||
'aaaa_ttl': int(aaaa_ttl),
|
||||
|
||||
@@ -106,7 +106,7 @@ class TestDnssd(thread_cert.TestCase):
|
||||
'weight': 1,
|
||||
'host': 'host1.default.service.arpa.',
|
||||
'address': client1_addrs,
|
||||
'txt_data': b'\x00',
|
||||
'txt_data': '',
|
||||
'srv_ttl': lambda x: x > 0,
|
||||
'txt_ttl': lambda x: x > 0,
|
||||
'aaaa_ttl': lambda x: x > 0,
|
||||
@@ -118,7 +118,7 @@ class TestDnssd(thread_cert.TestCase):
|
||||
'weight': 2,
|
||||
'host': 'host2.default.service.arpa.',
|
||||
'address': client2_addrs,
|
||||
'txt_data': b'\x00',
|
||||
'txt_data': '',
|
||||
'srv_ttl': lambda x: x > 0,
|
||||
'txt_ttl': lambda x: x > 0,
|
||||
'aaaa_ttl': lambda x: x > 0,
|
||||
|
||||
@@ -1099,6 +1099,209 @@ void TestHeaderAndResourceRecords(void)
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
void TestDnsTxtEntry(void)
|
||||
{
|
||||
enum
|
||||
{
|
||||
kMaxTxtDataSize = 255,
|
||||
};
|
||||
|
||||
struct EncodedTxtData
|
||||
{
|
||||
const uint8_t *mData;
|
||||
uint8_t mLength;
|
||||
};
|
||||
|
||||
const char kKey1[] = "key";
|
||||
const uint8_t kValue1[] = {'v', 'a', 'l', 'u', 'e'};
|
||||
|
||||
const char kKey2[] = "E";
|
||||
const uint8_t kValue2[] = {'m', 'c', '^', '2'};
|
||||
|
||||
const char kKey3[] = "space key";
|
||||
const uint8_t kValue3[] = {'=', 0, '='};
|
||||
|
||||
const char kKey4[] = "123456789"; // Max recommended length key
|
||||
const uint8_t kValue4[] = {0};
|
||||
|
||||
const char kKey5[] = "1234567890"; // Longer than recommended key
|
||||
const uint8_t kValue5[] = {'a'};
|
||||
|
||||
const char kKey6[] = "boolKey"; // Should be encoded as "boolKey" (without `=`).
|
||||
const char kKey7[] = "emptyKey"; // Should be encoded as "emptyKey=".
|
||||
|
||||
// Invalid key
|
||||
const char kShortKey[] = "";
|
||||
|
||||
const uint8_t kEncodedTxt1[] = {9, 'k', 'e', 'y', '=', 'v', 'a', 'l', 'u', 'e'};
|
||||
const uint8_t kEncodedTxt2[] = {6, 'E', '=', 'm', 'c', '^', '2'};
|
||||
const uint8_t kEncodedTxt3[] = {13, 's', 'p', 'a', 'c', 'e', ' ', 'k', 'e', 'y', '=', '=', 0, '='};
|
||||
const uint8_t kEncodedTxt4[] = {11, '1', '2', '3', '4', '5', '6', '7', '8', '9', '=', 0};
|
||||
const uint8_t kEncodedTxt5[] = {12, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '=', 'a'};
|
||||
const uint8_t kEncodedTxt6[] = {7, 'b', 'o', 'o', 'l', 'K', 'e', 'y'};
|
||||
const uint8_t kEncodedTxt7[] = {9, 'e', 'm', 'p', 't', 'y', 'K', 'e', 'y', '='};
|
||||
|
||||
const uint8_t kInvalidEncodedTxt1[] = {4, 'a', '=', 'b'}; // Incorrect length
|
||||
|
||||
// Special encoded txt data with zero strings and string starting
|
||||
// with '=' (missing key) whcih should be skipped over silently.
|
||||
const uint8_t kSpecialEncodedTxt[] = {0, 0, 3, 'A', '=', 'B', 2, '=', 'C', 3, 'D', '=', 'E', 3, '=', '1', '2'};
|
||||
|
||||
const Dns::TxtEntry kTxtEntries[] = {
|
||||
Dns::TxtEntry(kKey1, kValue1, sizeof(kValue1)),
|
||||
Dns::TxtEntry(kKey2, kValue2, sizeof(kValue2)),
|
||||
Dns::TxtEntry(kKey3, kValue3, sizeof(kValue3)),
|
||||
Dns::TxtEntry(kKey4, kValue4, sizeof(kValue4)),
|
||||
Dns::TxtEntry(kKey5, kValue5, sizeof(kValue5)),
|
||||
Dns::TxtEntry(kKey6, nullptr, 0),
|
||||
Dns::TxtEntry(kKey7, kValue1, 0),
|
||||
};
|
||||
|
||||
const EncodedTxtData kEncodedTxtData[] = {
|
||||
{kEncodedTxt1, sizeof(kEncodedTxt1)}, {kEncodedTxt2, sizeof(kEncodedTxt2)},
|
||||
{kEncodedTxt3, sizeof(kEncodedTxt3)}, {kEncodedTxt4, sizeof(kEncodedTxt4)},
|
||||
{kEncodedTxt5, sizeof(kEncodedTxt5)}, {kEncodedTxt6, sizeof(kEncodedTxt6)},
|
||||
{kEncodedTxt7, sizeof(kEncodedTxt7)}};
|
||||
|
||||
Instance * instance;
|
||||
MessagePool * messagePool;
|
||||
Message * message;
|
||||
uint8_t txtData[kMaxTxtDataSize];
|
||||
uint16_t txtDataLength;
|
||||
uint8_t index;
|
||||
Dns::TxtEntry txtEntry;
|
||||
Dns::TxtEntry::Iterator iterator;
|
||||
|
||||
printf("================================================================\n");
|
||||
printf("TestDnsTxtEntry()\n");
|
||||
|
||||
instance = static_cast<Instance *>(testInitInstance());
|
||||
VerifyOrQuit(instance != nullptr, "Null OpenThread instance");
|
||||
|
||||
messagePool = &instance->Get<MessagePool>();
|
||||
VerifyOrQuit((message = messagePool->New(Message::kTypeIp6, 0)) != nullptr, "Message::New failed");
|
||||
|
||||
SuccessOrQuit(Dns::TxtEntry::AppendEntries(kTxtEntries, OT_ARRAY_LENGTH(kTxtEntries), *message),
|
||||
"TxtEntry::AppendEntries() failed");
|
||||
|
||||
txtDataLength = message->GetLength();
|
||||
VerifyOrQuit(txtDataLength < kMaxTxtDataSize, "TXT data is too long");
|
||||
|
||||
SuccessOrQuit(message->Read(0, txtData, txtDataLength), "Failed to read txt data from message");
|
||||
DumpBuffer("txt data", txtData, txtDataLength);
|
||||
|
||||
index = 0;
|
||||
for (const EncodedTxtData &encodedData : kEncodedTxtData)
|
||||
{
|
||||
VerifyOrQuit(memcmp(&txtData[index], encodedData.mData, encodedData.mLength) == 0,
|
||||
"TxtData is incorrectly encoded");
|
||||
index += encodedData.mLength;
|
||||
}
|
||||
|
||||
iterator.Init(txtData, txtDataLength);
|
||||
|
||||
for (const Dns::TxtEntry &expectedTxtEntry : kTxtEntries)
|
||||
{
|
||||
uint8_t expectedKeyLength = static_cast<uint8_t>(strlen(expectedTxtEntry.mKey));
|
||||
|
||||
SuccessOrQuit(iterator.GetNextEntry(txtEntry), "TxtEntry::GetNextEntry() failed");
|
||||
printf("key:\"%s\" valueLen:%d\n", txtEntry.mKey != nullptr ? txtEntry.mKey : "(null)", txtEntry.mValueLength);
|
||||
|
||||
if (expectedKeyLength > Dns::TxtEntry::kMaxKeyLength)
|
||||
{
|
||||
// When the key is longer than recommended max key length,
|
||||
// the full encoded string is returned in `mValue` and
|
||||
// `mValueLength` and `mKey` should be set to `nullptr`.
|
||||
|
||||
VerifyOrQuit(txtEntry.mKey == nullptr, "TxtEntry key does not match expected value for long key");
|
||||
VerifyOrQuit(txtEntry.mValueLength == expectedKeyLength + expectedTxtEntry.mValueLength + sizeof(char),
|
||||
"TxtEntry value length is incorrect for long key");
|
||||
VerifyOrQuit(memcmp(txtEntry.mValue, expectedTxtEntry.mKey, expectedKeyLength) == 0,
|
||||
"txtEntry value does match expected content");
|
||||
VerifyOrQuit(txtEntry.mValue[expectedKeyLength] == static_cast<uint8_t>('='),
|
||||
"txtEntry value does match expected content");
|
||||
VerifyOrQuit(memcmp(&txtEntry.mValue[expectedKeyLength + sizeof(uint8_t)], expectedTxtEntry.mValue,
|
||||
expectedTxtEntry.mValueLength) == 0,
|
||||
"txtEntry value does match expected content");
|
||||
continue;
|
||||
}
|
||||
|
||||
VerifyOrQuit(strcmp(txtEntry.mKey, expectedTxtEntry.mKey) == 0, "TxtEntry key does not match expected value");
|
||||
VerifyOrQuit(txtEntry.mValueLength == expectedTxtEntry.mValueLength,
|
||||
"TxtEntry value length does not match expected value");
|
||||
|
||||
if (txtEntry.mValueLength != 0)
|
||||
{
|
||||
VerifyOrQuit(memcmp(txtEntry.mValue, expectedTxtEntry.mValue, txtEntry.mValueLength) == 0,
|
||||
"TxtEntry value does not match expected content");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ensure both `txtEntry.mKey` and `expectedTxtEntry.mKey` are
|
||||
// null or both are non-null (for boolean or empty keys).
|
||||
VerifyOrQuit((txtEntry.mKey == nullptr) == (expectedTxtEntry.mKey == nullptr),
|
||||
"TxtEntry value does not match expected value for bool or empty key");
|
||||
}
|
||||
}
|
||||
|
||||
VerifyOrQuit(iterator.GetNextEntry(txtEntry) == OT_ERROR_NOT_FOUND, "GetNextEntry() returned unexpected entry");
|
||||
VerifyOrQuit(iterator.GetNextEntry(txtEntry) == OT_ERROR_NOT_FOUND, "GetNextEntry() succeeded after done");
|
||||
|
||||
// Verify `AppendEntries()` correctly rejecting invalid key
|
||||
txtEntry.mValue = kValue1;
|
||||
txtEntry.mValueLength = sizeof(kValue1);
|
||||
txtEntry.mKey = kShortKey;
|
||||
VerifyOrQuit(Dns::TxtEntry::AppendEntries(&txtEntry, 1, *message) == OT_ERROR_INVALID_ARGS,
|
||||
"AppendEntries() did not fail with invalid key");
|
||||
|
||||
// Verify appending empty txt data
|
||||
|
||||
SuccessOrQuit(message->SetLength(0), "Message::SetLength() failed");
|
||||
SuccessOrQuit(Dns::TxtEntry::AppendEntries(nullptr, 0, *message), "AppendEntries() failed with empty array");
|
||||
txtDataLength = message->GetLength();
|
||||
VerifyOrQuit(txtDataLength == sizeof(uint8_t), "Data length is incorrect with empty array");
|
||||
SuccessOrQuit(message->Read(0, txtData, txtDataLength), "Failed to read txt data from message");
|
||||
VerifyOrQuit(txtData[0] == 0, "Data is invalid with empty array");
|
||||
|
||||
SuccessOrQuit(message->SetLength(0), "Message::SetLength() failed");
|
||||
txtEntry.mKey = nullptr;
|
||||
txtEntry.mValue = nullptr;
|
||||
txtEntry.mValueLength = 0;
|
||||
SuccessOrQuit(Dns::TxtEntry::AppendEntries(&txtEntry, 1, *message), "AppendEntries() failed with empty entry");
|
||||
txtDataLength = message->GetLength();
|
||||
VerifyOrQuit(txtDataLength == sizeof(uint8_t), "Data length is incorrect with empty entry");
|
||||
SuccessOrQuit(message->Read(0, txtData, txtDataLength), "Failed to read txt data from message");
|
||||
VerifyOrQuit(txtData[0] == 0, "Data is invalid with empty entry");
|
||||
|
||||
// Verify `Iterator` behavior with invalid txt data.
|
||||
|
||||
iterator.Init(kInvalidEncodedTxt1, sizeof(kInvalidEncodedTxt1));
|
||||
VerifyOrQuit(iterator.GetNextEntry(txtEntry) == OT_ERROR_PARSE, "GetNextEntry() did not fail with invalid data");
|
||||
|
||||
// Verify `GetNextEntry()` correctly skipping over empty strings and
|
||||
// strings starting with '=' (missing key) in encoded txt.
|
||||
//
|
||||
// kSpecialEncodedTxt:
|
||||
// { 0, 3, 'A', '=', 'B', 2, '=', 'C', 3, 'D', '=', 'E', 3, '=', '1', '2' }
|
||||
|
||||
iterator.Init(kSpecialEncodedTxt, sizeof(kSpecialEncodedTxt));
|
||||
|
||||
// We should get "A=B` (or key="A", and value="B")
|
||||
SuccessOrQuit(iterator.GetNextEntry(txtEntry), "GetNextEntry() failed");
|
||||
VerifyOrQuit((txtEntry.mKey[0] == 'A') && (txtEntry.mKey[1] == '\0'), "GetNextEntry() got incorrect key");
|
||||
VerifyOrQuit((txtEntry.mValueLength == 1) && (txtEntry.mValue[0] == 'B'), "GetNextEntry() got incorrect value");
|
||||
|
||||
// We should get "D=E` (or key="D", and value="E")
|
||||
SuccessOrQuit(iterator.GetNextEntry(txtEntry), "GetNextEntry() failed");
|
||||
VerifyOrQuit((txtEntry.mKey[0] == 'D') && (txtEntry.mKey[1] == '\0'), "GetNextEntry() got incorrect key");
|
||||
VerifyOrQuit((txtEntry.mValueLength == 1) && (txtEntry.mValue[0] == 'E'), "GetNextEntry() got incorrect value");
|
||||
|
||||
VerifyOrQuit(iterator.GetNextEntry(txtEntry) == OT_ERROR_NOT_FOUND, "GetNextEntry() returned extra entry");
|
||||
|
||||
message->Free();
|
||||
testFreeInstance(instance);
|
||||
}
|
||||
|
||||
} // namespace ot
|
||||
|
||||
int main(void)
|
||||
@@ -1106,6 +1309,7 @@ int main(void)
|
||||
ot::TestDnsName();
|
||||
ot::TestDnsCompressedName();
|
||||
ot::TestHeaderAndResourceRecords();
|
||||
ot::TestDnsTxtEntry();
|
||||
|
||||
printf("All tests passed\n");
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user