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()