[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:
Abtin Keshavarzian
2021-02-19 16:17:35 -08:00
committed by GitHub
parent 196aa39c38
commit 14ef4e4082
19 changed files with 553 additions and 199 deletions
+55 -9
View File
@@ -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);
/**
* @}
*
+1 -1
View File
@@ -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
+5 -10
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+2
View File
@@ -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);
+1 -2
View File
@@ -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);
+9 -35
View File
@@ -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("");
}
}
-1
View File
@@ -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[] = {
+11
View File
@@ -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)
+6 -5
View File
@@ -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)
+88 -58
View File
@@ -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
+91 -30
View File
@@ -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;
/**
+4 -16
View File
@@ -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);
-5
View File
@@ -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);
+11 -8
View File
@@ -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.
+12 -12
View File
@@ -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),
+2 -2
View File
@@ -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,
+204
View File
@@ -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;