From 46f796d4f041c80c716b1ede81eba13c9b9e4f06 Mon Sep 17 00:00:00 2001 From: kangping Date: Fri, 29 Jan 2021 13:12:56 +0800 Subject: [PATCH] [srp] get parsed TXT entry from SRP server (#6113) This commit returns parsed TXT entries for service from the SRP server, it enables prettier CLI output and easier Advertising Proxy processing. We moved the otSrpTxtEntry to include/openthread/dns.h because this structure can ( and will) also be used by DNS-SD client. We also added one more mKeyLength field in otDnsTxtEntry for saving length of the TXT key so that we can iterate over the encoded TXT data without dynamically allocating memory for each entry. --- include/openthread/dns.h | 39 ++++++ include/openthread/instance.h | 2 +- include/openthread/srp_client.h | 38 +---- include/openthread/srp_server.h | 16 ++- src/cli/cli_srp_client.cpp | 1 + src/cli/cli_srp_client.hpp | 2 +- src/cli/cli_srp_server.cpp | 82 +++++++---- src/cli/cli_srp_server.hpp | 3 + src/core/api/srp_server_api.cpp | 7 +- src/core/net/dns_headers.cpp | 131 ++++++++++++++++++ src/core/net/dns_headers.hpp | 87 ++++++++++++ src/core/net/srp_client.cpp | 64 +-------- src/core/net/srp_client.hpp | 50 +------ src/core/net/srp_server.cpp | 6 + src/core/net/srp_server.hpp | 15 +- tests/scripts/thread-cert/node.py | 28 +++- .../test_srp_register_single_service.py | 8 +- 17 files changed, 382 insertions(+), 197 deletions(-) diff --git a/include/openthread/dns.h b/include/openthread/dns.h index a96478109..f4d44ce1f 100644 --- a/include/openthread/dns.h +++ b/include/openthread/dns.h @@ -57,6 +57,45 @@ extern "C" { #define OT_DNS_DEFAULT_SERVER_IP "2001:4860:4860::8888" ///< Defines default DNS Server address - Google DNS. #define OT_DNS_DEFAULT_SERVER_PORT 53 ///< Defines default DNS Server port. +/** + * Initializer for otDnsTxtIterator. + */ +#define OT_DNS_TXT_ITERATOR_INIT 0 + +typedef uint16_t otDnsTxtIterator; ///< Used to iterate through the TXT entries. + +/** + * This structure represents a TXT record entry representing a key/value pair (RFC 6763 - section 6.3). + * + * The string buffers pointed to by `mKey` and `mValue` MUST persist and remain unchanged after an instance of such + * structure is passed to OpenThread (as part of `otSrpClientService` instance). + * + * An array of `otDnsTxtEntry` entries are used in `otSrpClientService` to specify the full TXT record (a list of + * entries). + * + */ +typedef struct otDnsTxtEntry +{ + /** + * The TXT record key string. It doesn't need to be a null-terminated string and `mKeyLength` gives its length. + * + * If `mKey` is not NULL, then 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. + * - If `mValue` is NULL, then key is treated as a boolean attribute and encoded as "key" (with no `=`). + * - If `mValue` is not NULL but `mValueLength` is zero, then it is treated as empty value and encoded as "key=". + * + * If `mKey` is NULL, then `mValue` buffer is treated as an already encoded TXT-DATA and is appended as is in the + * DNS message. + * + */ + 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 implements DNS Query parameters. * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 09b1a2b54..c1b4db1b1 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (66) +#define OPENTHREAD_API_VERSION (67) /** * @addtogroup api-instance diff --git a/include/openthread/srp_client.h b/include/openthread/srp_client.h index 58928bb90..6a4021b46 100644 --- a/include/openthread/srp_client.h +++ b/include/openthread/srp_client.h @@ -35,6 +35,7 @@ #ifndef OPENTHREAD_SRP_CLIENT_H_ #define OPENTHREAD_SRP_CLIENT_H_ +#include #include #ifdef __cplusplus @@ -51,37 +52,6 @@ extern "C" { * */ -/** - * This structure represents a TXT record entry representing a key/value pair (RFC 6763 - section 6.3). - * - * The strings buffers pointed to by `mKey` and `mValue` MUST persist and remain unchanged after an instance of such a - * structure is passed to OpenThread (as part of `otSrpClientService` instance). - * - * An array of `otSrpTxtEntry` entries is used in `otSrpClientService` to specify the full TXT record (a list of - * entries). - * - */ -typedef struct otSrpTxtEntry -{ - /** - * 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. - * - 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. - * - If `mValue` is NULL, then key is treated as a boolean attribute and encoded as "key" (with no `=`). - * - If `mValue` is not NULL but `mValueLength` is zero, then it is treated as empty value and encoded as "key=". - * - * If `mKey` is NULL, then `mValue` buffer is treated as an already encoded TXT-DATA and is appended as is in the - * DNS message. - * - */ - 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. -} otSrpTxtEntry; - /** * This enumeration specifies an SRP client item (service or host info) state. * @@ -122,7 +92,7 @@ typedef struct otSrpClientService { const char * mName; ///< The service name labels (e.g., "_chip._udp", not the full domain name). const char * mInstanceName; ///< The service instance name label (not the full name). - const otSrpTxtEntry *mTxtEntries; ///< Array of TXT entries (number of entries is given by `mNumTxtEntries`). + const otDnsTxtEntry *mTxtEntries; ///< Array of TXT entries (number of entries is given by `mNumTxtEntries`). uint16_t mPort; ///< The service port number. uint16_t mPriority; ///< The service priority. uint16_t mWeight; ///< The service weight. @@ -179,7 +149,7 @@ typedef struct otSrpClientService * The following errors are also possible: * * OT_ERROR_RESPONSE_TIMEOUT : Timed out waiting for response from server (client would continue to retry). - * OT_ERROR_INVALID_ARGS : The provided service structure is invalid (e.g., bad service name or `otSrpTxtEntry`). + * OT_ERROR_INVALID_ARGS : The provided service structure is invalid (e.g., bad service name or `otDnsTxtEntry`). * OT_ERROR_NO_BUFS : Insufficient buffer to prepare or send the update message. * * Note that in case of any failure, the client continues the operation, i.e. it prepares and (re)transmits the SRP @@ -377,7 +347,7 @@ otError otSrpClientSetHostAddresses(otInstance *aInstance, const otIp6Address *a * @retval OT_ERROR_NONE The addition of service started successfully. The `otSrpClientCallback` will be * called to report the status. * @retval OT_ERROR_ALREADY The same service is already in the list. - * @retval OT_ERROR_INVALID_ARGS The service structure is invalid (e.g., bad service name or `otSrpTxtEntry`). + * @retval OT_ERROR_INVALID_ARGS The service structure is invalid (e.g., bad service name or `otDnsTxtEntry`). * */ otError otSrpClientAddService(otInstance *aInstance, otSrpClientService *aService); diff --git a/include/openthread/srp_server.h b/include/openthread/srp_server.h index d3a4b06dc..c4cf3381c 100644 --- a/include/openthread/srp_server.h +++ b/include/openthread/srp_server.h @@ -37,6 +37,7 @@ #include +#include #include #include @@ -303,15 +304,20 @@ uint16_t otSrpServerServiceGetWeight(const otSrpServerService *aService); uint16_t otSrpServerServiceGetPriority(const otSrpServerService *aService); /** - * This method returns the TXT data of the service instance. + * This method returns the next TXT entry of the service instance. * - * @param[in] aService A pointer to the SRP service. - * @param[out] aTxtLength A pointer to the output of the TXT data length. + * @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. * - * @returns A pointer to the standard TXT data with format described by RFC 6763. + * @retval OT_ERROR_NONE Successfully found the next TXT entry. + * @retval OT_ERROR_NOT_FOUND No subsequent TXT entry exists in the service. * */ -const uint8_t *otSrpServerServiceGetTxtData(const otSrpServerService *aService, uint16_t *aTxtLength); +otError otSrpServerServiceGetNextTxtEntry(const otSrpServerService *aService, + otDnsTxtIterator * aIterator, + otDnsTxtEntry * aTxtEntry); /** * This method returns the host which the service instance reside on. diff --git a/src/cli/cli_srp_client.cpp b/src/cli/cli_srp_client.cpp index f8c02750e..15f9a4fea 100644 --- a/src/cli/cli_srp_client.cpp +++ b/src/cli/cli_srp_client.cpp @@ -323,6 +323,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.mValue = entry->mTxtBuffer; entry->mTxtEntry.mValueLength = sizeof(entry->mTxtBuffer); diff --git a/src/cli/cli_srp_client.hpp b/src/cli/cli_srp_client.hpp index 22504e5ed..aef6562e0 100644 --- a/src/cli/cli_srp_client.hpp +++ b/src/cli/cli_srp_client.hpp @@ -88,7 +88,7 @@ private: bool IsInUse(void) const { return (mService.mNext != &mService); } otSrpClientService mService; - otSrpTxtEntry mTxtEntry; + otDnsTxtEntry mTxtEntry; char mInstanceName[kNameSize]; char mServiceName[kNameSize]; uint8_t mTxtBuffer[kTxtSize]; diff --git a/src/cli/cli_srp_server.cpp b/src/cli/cli_srp_server.cpp index 59eaf6d61..a6e3bee47 100644 --- a/src/cli/cli_srp_server.cpp +++ b/src/cli/cli_srp_server.cpp @@ -163,6 +163,53 @@ 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; + uint8_t addressesNum; + + addresses = otSrpServerHostGetAddresses(aHost, &addressesNum); + + mInterpreter.OutputFormat("["); + for (uint8_t i = 0; i < addressesNum; ++i) + { + if (i != 0) + { + mInterpreter.OutputFormat(", "); + } + + mInterpreter.OutputIp6Address(addresses[i]); + } + mInterpreter.OutputFormat("]"); +} + otError SrpServer::ProcessService(uint8_t aArgsLength, char *aArgs[]) { OT_UNUSED_VARIABLE(aArgs); @@ -179,11 +226,7 @@ otError SrpServer::ProcessService(uint8_t aArgsLength, char *aArgs[]) while ((service = otSrpServerHostGetNextService(host, service)) != nullptr) { - const otIp6Address *addresses; - uint8_t addressesNum; - const uint8_t * txtData; - uint16_t txtLength; - bool isDeleted = otSrpServerServiceIsDeleted(service); + bool isDeleted = otSrpServerServiceIsDeleted(service); mInterpreter.OutputLine(otSrpServerServiceGetFullName(service)); mInterpreter.OutputLine(Interpreter::kIndentSize, "deleted: %s", isDeleted ? "true" : "false"); @@ -196,30 +239,17 @@ otError SrpServer::ProcessService(uint8_t aArgsLength, char *aArgs[]) mInterpreter.OutputLine(Interpreter::kIndentSize, "priority: %hu", otSrpServerServiceGetPriority(service)); mInterpreter.OutputLine(Interpreter::kIndentSize, "weight: %hu", otSrpServerServiceGetWeight(service)); - txtData = otSrpServerServiceGetTxtData(service, &txtLength); - - if (txtLength > 0) - { - mInterpreter.OutputSpaces(Interpreter::kIndentSize); - mInterpreter.OutputFormat("TXT: "); - mInterpreter.OutputBytes(txtData, txtLength); - mInterpreter.OutputFormat("\r\n"); - } + mInterpreter.OutputSpaces(Interpreter::kIndentSize); + mInterpreter.OutputFormat("TXT: "); + OutputServiceTxtEntries(service); + mInterpreter.OutputFormat("\r\n"); mInterpreter.OutputLine(Interpreter::kIndentSize, "host: %s", otSrpServerHostGetFullName(host)); - mInterpreter.OutputSpaces(Interpreter::kIndentSize); - mInterpreter.OutputFormat("addresses: ["); - addresses = otSrpServerHostGetAddresses(host, &addressesNum); - for (uint8_t i = 0; i < addressesNum; ++i) - { - mInterpreter.OutputIp6Address(addresses[i]); - if (i < addressesNum - 1) - { - mInterpreter.OutputFormat(", "); - } - } - mInterpreter.OutputFormat("]\r\n"); + mInterpreter.OutputSpaces(Interpreter::kIndentSize); + mInterpreter.OutputFormat("addresses: "); + OutputHostAddresses(host); + mInterpreter.OutputFormat("\r\n"); } } diff --git a/src/cli/cli_srp_server.hpp b/src/cli/cli_srp_server.hpp index e40de9bd9..ff45acd36 100644 --- a/src/cli/cli_srp_server.hpp +++ b/src/cli/cli_srp_server.hpp @@ -92,6 +92,9 @@ 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[] = { {"disable", &SrpServer::ProcessDisable}, {"domain", &SrpServer::ProcessDomain}, {"enable", &SrpServer::ProcessEnable}, {"help", &SrpServer::ProcessHelp}, diff --git a/src/core/api/srp_server_api.cpp b/src/core/api/srp_server_api.cpp index 059118751..0af1f7e0c 100644 --- a/src/core/api/srp_server_api.cpp +++ b/src/core/api/srp_server_api.cpp @@ -147,9 +147,12 @@ uint16_t otSrpServerServiceGetPriority(const otSrpServerService *aService) return static_cast(aService)->GetPriority(); } -const uint8_t *otSrpServerServiceGetTxtData(const otSrpServerService *aService, uint16_t *aTxtLength) +otError otSrpServerServiceGetNextTxtEntry(const otSrpServerService *aService, + otDnsTxtIterator * aIterator, + otDnsTxtEntry * aTxtEntry) { - return static_cast(aService)->GetTxtData(*aTxtLength); + return static_cast(aService)->GetNextTxtEntry( + *aIterator, static_cast(*aTxtEntry)); } const otSrpServerHost *otSrpServerServiceGetHost(const otSrpServerService *aService) diff --git a/src/core/net/dns_headers.cpp b/src/core/net/dns_headers.cpp index f9abfcc77..f469ff542 100644 --- a/src/core/net/dns_headers.cpp +++ b/src/core/net/dns_headers.cpp @@ -702,6 +702,64 @@ exit: return error; } +otError TxtEntry::AppendTo(Message &aMessage) const +{ + otError error = OT_ERROR_NONE; + uint8_t length; + + if (mKey == nullptr) + { + VerifyOrExit(mValue != nullptr); + error = aMessage.AppendBytes(mValue, mValueLength); + ExitNow(); + } + + length = mKeyLength; + + VerifyOrExit(length <= kMaxKeyLength, 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); + 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); + + length += static_cast(mValueLength + sizeof(char)); + + SuccessOrExit(error = aMessage.Append(length)); + SuccessOrExit(error = aMessage.AppendBytes(mKey, length)); + SuccessOrExit(error = aMessage.Append(kKeyValueSeparator)); + error = aMessage.AppendBytes(mValue, mValueLength); + +exit: + return error; +} + +otError TxtEntry::AppendEntries(const TxtEntry *aEntries, uint8_t aNumEntries, Message &aMessage) +{ + otError error = OT_ERROR_NONE; + uint16_t startOffset = aMessage.GetLength(); + + for (uint8_t index = 0; index < aNumEntries; index++) + { + SuccessOrExit(error = aEntries[index].AppendTo(aMessage)); + } + + if (aMessage.GetLength() == startOffset) + { + error = aMessage.Append(0); + } + +exit: + return error; +} + bool AaaaRecord::IsValid(void) const { return GetType() == Dns::ResourceRecord::kTypeAaaa && GetSize() == sizeof(*this); @@ -774,6 +832,7 @@ otError TxtRecord::ReadTxtData(const Message &aMessage, VerifyOrExit(GetLength() <= aTxtBufferSize, error = OT_ERROR_NO_BUFS); SuccessOrExit(error = aMessage.Read(aOffset, aTxtBuffer, GetLength())); + VerifyOrExit(VerifyTxtData(aTxtBuffer, GetLength()), error = OT_ERROR_PARSE); aTxtBufferSize = GetLength(); aOffset += GetLength(); @@ -781,5 +840,77 @@ exit: return error; } +bool TxtRecord::VerifyTxtData(const uint8_t *aTxtData, uint16_t aTxtLength) +{ + bool valid = false; + uint8_t curEntryLength = 0; + + // Per RFC 1035, TXT-DATA MUST have one or more s. + VerifyOrExit(aTxtLength > 0); + + for (uint16_t i = 0; i < aTxtLength; ++i) + { + if (curEntryLength == 0) + { + curEntryLength = aTxtData[i]; + } + else + { + --curEntryLength; + } + } + + valid = (curEntryLength == 0); + +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(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 diff --git a/src/core/net/dns_headers.hpp b/src/core/net/dns_headers.hpp index c400a409f..43d8832c5 100644 --- a/src/core/net/dns_headers.hpp +++ b/src/core/net/dns_headers.hpp @@ -36,6 +36,8 @@ #include "openthread-core-config.h" +#include + #include "common/clearable.hpp" #include "common/encoding.hpp" #include "common/message.hpp" @@ -955,6 +957,56 @@ private: uint16_t mOffset; // Offset in `mMessage` to the start of name (used when name is from `mMessage`). }; +/** + * This type represents a TXT record entry representing a key/value pair (RFC 6763 - section 6.3). + * + */ +class TxtEntry : public otDnsTxtEntry +{ + friend class TxtRecord; + +public: + /** + * This method encodes and appends the `TxtEntry` to a message. + * + * @param[in] aMessage The message to append to. + * + * @retval OT_ERROR_NONE Entry was 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. + * + */ + otError AppendTo(Message &aMessage) const; + + /** + * This static method appends an array of `TxtEntry` items to a message. + * + * @param[in] aEntries A pointer to array of `TxtEntry` items. + * @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. + * + */ + static otError AppendEntries(const TxtEntry *aEntries, uint8_t aNumEntries, Message &aMessage); + +private: + enum : char + { + kKeyValueSeparator = '=', + }; + + enum : uint8_t + { + kMinKeyLength = 1, + kMaxKeyLength = 9, + kMaxKeyValueEncodedSize = 255, + }; +}; + /** * This class implements Resource Record (RR) body format. * @@ -1308,6 +1360,8 @@ public: kType = kTypeTxt, ///< The TXT record type. }; + typedef otDnsTxtIterator TxtIterator; + /** * This method initializes the TXT Resource Record by setting its type and class. * @@ -1321,6 +1375,8 @@ public: /** * This method parses and reads the TXT record data from a message. * + * This method also checks if the TXT data is well-formed by calling `VerifyTxtData()`. + * * @param[in] aMessage The message to read from. * @param[inout] aOffset On input, the offset in @p aMessage to start of TXT record data. * On exit when successfully read, @p aOffset is updated to point to the byte @@ -1340,6 +1396,37 @@ public: uint8_t * aTxtBuffer, uint16_t & aTxtBufferSize) const; + /** + * This static method tests if a buffer contains valid encoded TXT data. + * + * @param[in] aTxtData The TXT data buffer. + * @param[in] aTxtLength The length of the TXT data buffer. + * + * @returns TRUE if @p aTxtData contains valid encoded TXT data, FALSE if not. + * + */ + 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; /** diff --git a/src/core/net/srp_client.cpp b/src/core/net/srp_client.cpp index 2796e90a8..4ba071520 100644 --- a/src/core/net/srp_client.cpp +++ b/src/core/net/srp_client.cpp @@ -46,67 +46,6 @@ namespace ot { namespace Srp { -//--------------------------------------------------------------------- -// Client::TxtEntry - -otError Client::TxtEntry::AppendTo(Message &aMessage) const -{ - otError error = OT_ERROR_NONE; - uint8_t length; - - if (mKey == nullptr) - { - VerifyOrExit(mValue != nullptr); - error = aMessage.AppendBytes(mValue, mValueLength); - ExitNow(); - } - - length = static_cast(StringLength(mKey, kMaxKeyLength + 1)); - - VerifyOrExit(length <= kMaxKeyLength, 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); - 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); - - length += static_cast(mValueLength + sizeof(char)); - - SuccessOrExit(error = aMessage.Append(length)); - SuccessOrExit(error = aMessage.AppendBytes(mKey, length)); - SuccessOrExit(error = aMessage.Append(kKeyValueSeparator)); - error = aMessage.AppendBytes(mValue, mValueLength); - -exit: - return error; -} - -otError Client::TxtEntry::AppendEntries(const TxtEntry *aEntries, uint8_t aNumEntries, Message &aMessage) -{ - otError error = OT_ERROR_NONE; - uint16_t startOffset = aMessage.GetLength(); - - for (uint8_t index = 0; index < aNumEntries; index++) - { - SuccessOrExit(error = aEntries[index].AppendTo(aMessage)); - } - - if (aMessage.GetLength() == startOffset) - { - error = aMessage.Append(0); - } - -exit: - return error; -} - //--------------------------------------------------------------------- // Client::HostInfo @@ -834,7 +773,8 @@ otError Client::AppendServiceInstructions(Service &aService, Message &aMessage, rr.Init(Dns::ResourceRecord::kTypeTxt); offset = aMessage.GetLength(); SuccessOrExit(error = aMessage.Append(rr)); - SuccessOrExit(error = TxtEntry::AppendEntries(aService.GetTxtEntries(), aService.GetNumTxtEntries(), aMessage)); + SuccessOrExit(error = + Dns::TxtEntry::AppendEntries(aService.GetTxtEntries(), aService.GetNumTxtEntries(), aMessage)); UpdateRecordLengthInMessage(rr, offset, aMessage); aInfo.mRecordCount++; diff --git a/src/core/net/srp_client.hpp b/src/core/net/srp_client.hpp index 033ed2b04..d86c60464 100644 --- a/src/core/net/srp_client.hpp +++ b/src/core/net/srp_client.hpp @@ -92,54 +92,6 @@ public: */ typedef otSrpClientCallback Callback; - /** - * This type represents a TXT record entry representing a key/value pair (RFC 6763 - section 6.3). - * - */ - class TxtEntry : public otSrpTxtEntry - { - public: - /** - * This method encodes and appends the `TxtEntry` to a message. - * - * @param[in] aMessage The message to append to. - * - * @retval OT_ERROR_NONE Entry was appended successfully to @p aMessage. - * @retval OT_ERROR_INVALID_ARGS The `TxEntry` info is not valid. - * @retval OT_ERROR_NO_BUFS Insufficient available buffers to grow the message. - * - */ - otError AppendTo(Message &aMessage) const; - - /** - * This static method appends an array of `TxtEntry` items to a message. - * - * @param[in] aEntries A pointer to array of `TxtEntry` items. - * @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 `TxEntry` info is not valid. - * @retval OT_ERROR_NO_BUFS Insufficient available buffers to grow the message. - * - */ - static otError AppendEntries(const TxtEntry *aEntries, uint8_t aNumEntries, Message &aMessage); - - private: - enum : char - { - kKeyValueSeparator = '=', - }; - - enum : uint8_t - { - kMinKeyLength = 1, - kMaxKeyLength = 9, - kMaxKeyValueEncodedSize = 255, - }; - }; - /** * This type represents an SRP client host info. * @@ -268,7 +220,7 @@ public: * @returns A pointer to an array of service TXT entries. * */ - const TxtEntry *GetTxtEntries(void) const { return static_cast(mTxtEntries); } + const Dns::TxtEntry *GetTxtEntries(void) const { return static_cast(mTxtEntries); } /** * This method gets the number of entries in the service TXT entry array. diff --git a/src/core/net/srp_server.cpp b/src/core/net/srp_server.cpp index f0b534bdc..b1d231aa2 100644 --- a/src/core/net/srp_server.cpp +++ b/src/core/net/srp_server.cpp @@ -1324,6 +1324,11 @@ 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); @@ -1366,6 +1371,7 @@ otError Server::Service::SetTxtDataFromMessage(const Message &aMessage, uint16_t txtData = static_cast(GetInstance().HeapCAlloc(1, aLength)); VerifyOrExit(txtData != nullptr, error = OT_ERROR_NO_BUFS); VerifyOrExit(aMessage.ReadBytes(aOffset, txtData, aLength) == aLength, error = OT_ERROR_PARSE); + VerifyOrExit(Dns::TxtRecord::VerifyTxtData(txtData, aLength), error = OT_ERROR_PARSE); GetInstance().HeapFree(mTxtData); mTxtData = txtData; diff --git a/src/core/net/srp_server.hpp b/src/core/net/srp_server.hpp index 535c64cd9..cc76cb53b 100644 --- a/src/core/net/srp_server.hpp +++ b/src/core/net/srp_server.hpp @@ -151,18 +151,17 @@ public: uint16_t GetPriority(void) const { return mPriority; } /** - * This method returns the TXT data of the service instance. + * This method returns the next TXT entry of the service instance. * - * @param[out] aTxtLength A pointer to the output of the TXT data length. + * @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. * - * @returns A pointer to the standard TXT data with format described by RFC 6763. + * @retval OT_ERROR_NONE Successfully found the next TXT entry. + * @retval OT_ERROR_NOT_FOUND No subsequent TXT entry exists in the service. * */ - const uint8_t *GetTxtData(uint16_t &aTxtLength) const - { - aTxtLength = mTxtLength; - return mTxtData; - } + otError GetNextTxtEntry(Dns::TxtRecord::TxtIterator &aIterator, Dns::TxtEntry &aTxtEntry) const; /** * This method returns the host which the service instance reside on. diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index d9257bcda..25aacdf03 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -757,7 +757,7 @@ class NodeImpl: 'port': '12345', 'priority': '0', 'weight': '0', - 'TXT': '00', + 'TXT': ['abc=010203'], 'host_fullname': 'my-host.default.service.arpa.', 'host': 'my-host', 'addresses': ['2001::1', '2001::2'] @@ -783,16 +783,20 @@ class NodeImpl: service_list.append(service) continue - # 'port', 'priority', 'weight', 'TXT' - for i in range(0, 4): + # 'port', 'priority', 'weight' + for i in range(0, 3): key_value = lines.pop(0).strip().split(':') service[key_value[0].strip()] = key_value[1].strip() + txt_entries = lines.pop(0).strip().split('[')[1].strip(' ]').split(',') + txt_entries = map(str.strip, txt_entries) + service['TXT'] = [txt for txt in txt_entries if txt] + service['host_fullname'] = lines.pop(0).strip().split(':')[1].strip() service['host'] = service['host_fullname'].split('.')[0] addresses = lines.pop(0).strip().split('[')[1].strip(' ]').split(',') - map(str.strip, addresses) + addresses = map(str.strip, addresses) service['addresses'] = [addr for addr in addresses if addr] service_list.append(service) @@ -860,8 +864,10 @@ class NodeImpl: self.send_command(f'srp client host address') self._expect_done() - def srp_client_add_service(self, instance_name, service_name, port): - self.send_command(f'srp client service add {instance_name} {service_name} {port}') + def srp_client_add_service(self, instance_name, service_name, port, priority=0, weight=0, txt_entries=[]): + txt_record = "".join(self._encode_txt_entry(entry) for entry in txt_entries) + self.send_command( + f'srp client service add {instance_name} {service_name} {port} {priority} {weight} {txt_record}') self._expect_done() def srp_client_remove_service(self, instance_name, service_name): @@ -874,6 +880,16 @@ class NodeImpl: service_lines = self._expect_command_output(cmd) return [self._parse_srp_client_service(line) for line in service_lines] + def _encode_txt_entry(self, entry): + """Encodes the TXT entry to the DNS-SD TXT record format as a HEX string. + + Example usage: + self._encode_txt_entries(['abc']) -> '03616263' + self._encode_txt_entries(['def=']) -> '046465663d' + self._encode_txt_entries(['xyz=XYZ']) -> '0778797a3d58595a' + """ + return '{:02x}'.format(len(entry)) + "".join("{:02x}".format(ord(c)) for c in entry) + def _parse_srp_client_service(self, line: str): """Parse one line of srp service list into a dictionary which maps string keys to string values. diff --git a/tests/scripts/thread-cert/test_srp_register_single_service.py b/tests/scripts/thread-cert/test_srp_register_single_service.py index 1ec45d05e..4252a8f1d 100755 --- a/tests/scripts/thread-cert/test_srp_register_single_service.py +++ b/tests/scripts/thread-cert/test_srp_register_single_service.py @@ -94,7 +94,7 @@ class SrpRegisterSingleService(thread_cert.TestCase): client.srp_client_set_host_name('my-host') client.srp_client_set_host_address('2001::1') client.srp_client_start(server.get_addrs()[0], client.get_srp_server_port()) - client.srp_client_add_service('my-service', '_ipps._tcp', 12345) + client.srp_client_add_service('my-service', '_ipps._tcp', 12345, 0, 0, ['abc', 'def=', 'xyz=XYZ']) self.simulator.go(2) self.check_host_and_service(server, client) @@ -135,7 +135,7 @@ class SrpRegisterSingleService(thread_cert.TestCase): # reused. # - client.srp_client_add_service('my-service', '_ipps._tcp', 12345) + client.srp_client_add_service('my-service', '_ipps._tcp', 12345, 0, 0, ['abc', 'def=', 'xyz=XYZ']) self.simulator.go(2) self.check_host_and_service(server, client) @@ -181,7 +181,6 @@ class SrpRegisterSingleService(thread_cert.TestCase): self.assertEqual(client_service['state'], 'Registered') server_services = server.srp_server_get_services() - print(server_services) self.assertEqual(len(server_services), 1) server_service = server_services[0] @@ -193,6 +192,9 @@ class SrpRegisterSingleService(thread_cert.TestCase): self.assertEqual(int(server_service['port']), int(client_service['port'])) self.assertEqual(int(server_service['priority']), int(client_service['priority'])) self.assertEqual(int(server_service['weight']), int(client_service['weight'])) + # We output value of TXT entry as HEX string. + print(server_service['TXT']) + self.assertEqual(server_service['TXT'], ['abc', 'def=', 'xyz=58595a']) self.assertEqual(server_service['host'], 'my-host') server_hosts = server.srp_server_get_hosts()