diff --git a/src/core/common/heap_data.cpp b/src/core/common/heap_data.cpp index fcdfe0524..5060fde34 100644 --- a/src/core/common/heap_data.cpp +++ b/src/core/common/heap_data.cpp @@ -82,6 +82,13 @@ void Data::TakeFrom(Data &&aData) } } +void Data::TakeFrom(uint8_t *&aHeapAllocatedBuffer, uint16_t aLength) +{ + Free(); + mData.Init(aHeapAllocatedBuffer, aLength); + aHeapAllocatedBuffer = nullptr; +} + bool Data::Matches(const uint8_t *aBuffer, uint16_t aLength) const { bool matches = false; diff --git a/src/core/common/heap_data.hpp b/src/core/common/heap_data.hpp index 76d1e40fb..c5f05d0a0 100644 --- a/src/core/common/heap_data.hpp +++ b/src/core/common/heap_data.hpp @@ -140,6 +140,19 @@ public: */ void TakeFrom(Data &&aData); + /** + * Sets the `Heap::Data` by taking ownership of a given heap-allocated buffer. + * + * After this call, the `Heap::Data` will take ownership of the buffer and free it when done. The + * @p aHeapAllocatedBuffer pointer is set to `nullptr` to ensure the caller does not retain a pointer to the + * transferred buffer. + * + * @param[in,out] aHeapAllocatedBuffer A reference to a pointer to a heap-allocated buffer. + * On exit, it is set to `nullptr`. + * @param[in] aLength The length of the buffer (number of bytes). + */ + void TakeFrom(uint8_t *&aHeapAllocatedBuffer, uint16_t aLength); + /** * Casts the `Heap::Data` to an rvalue reference. * diff --git a/src/core/meshcop/border_agent.cpp b/src/core/meshcop/border_agent.cpp index c06343dde..bba55fc8d 100644 --- a/src/core/meshcop/border_agent.cpp +++ b/src/core/meshcop/border_agent.cpp @@ -432,38 +432,19 @@ void Manager::ConstrcutServiceName(const char *aBaseName, Dns::Name::LabelBuffer void Manager::RegisterService(void) { Dnssd::Service service; - uint16_t vendorDataLength; - uint8_t *txtDataBuffer; - uint16_t txtDataBufferSize; - uint16_t txtDataLength; + Heap::Data txtData; VerifyOrExit(IsEnabled()); VerifyOrExit(Get().IsReady()); - // Allocate a large enough buffer to fit both the TXT data - // generated by Border Agent itself and the vendor extra - // TXT data. The vendor TXT Data is appended at the - // end. - - vendorDataLength = Get().GetVendorData().GetLength(); - txtDataBufferSize = kTxtDataMaxSize + vendorDataLength; - txtDataBuffer = reinterpret_cast(Heap::CAlloc(txtDataBufferSize, sizeof(uint8_t))); - OT_ASSERT(txtDataBuffer != nullptr); - - SuccessOrAssert(Get().Prepare(txtDataBuffer, txtDataBufferSize, txtDataLength)); - - if (vendorDataLength != 0) - { - Get().GetVendorData().CopyBytesTo(txtDataBuffer + txtDataLength); - txtDataLength += vendorDataLength; - } + Get().PrepareWithVendorData(txtData); service.Clear(); service.mServiceInstance = GetServiceName(); service.mServiceType = kServiceType; service.mPort = IsRunning() ? GetUdpPort() : kDummyUdpPort; - service.mTxtData = txtDataBuffer; - service.mTxtDataLength = txtDataLength; + service.mTxtData = txtData.GetBytes(); + service.mTxtDataLength = txtData.GetLength(); #if OPENTHREAD_CONFIG_BORDER_AGENT_ADMITTER_ENABLE if (Get().IsPrimeAdmitter()) @@ -475,8 +456,6 @@ void Manager::RegisterService(void) Get().RegisterService(service, /* aRequestId */ 0, /* aCallback */ nullptr); - Heap::Free(txtDataBuffer); - exit: return; } diff --git a/src/core/meshcop/border_agent.hpp b/src/core/meshcop/border_agent.hpp index 6f4c651eb..4da326f21 100644 --- a/src/core/meshcop/border_agent.hpp +++ b/src/core/meshcop/border_agent.hpp @@ -264,7 +264,6 @@ public: private: static constexpr uint16_t kUdpPort = OPENTHREAD_CONFIG_BORDER_AGENT_UDP_PORT; static constexpr uint32_t kKeepAliveTimeout = 50 * 1000; // Timeout to reject a commissioner (in msec) - static constexpr uint16_t kTxtDataMaxSize = OT_BORDER_AGENT_MESHCOP_SERVICE_TXT_DATA_MAX_LENGTH; #if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE static constexpr uint16_t kDummyUdpPort = 49152; diff --git a/src/core/meshcop/border_agent_txt_data.cpp b/src/core/meshcop/border_agent_txt_data.cpp index 4ea20406c..c57416d69 100644 --- a/src/core/meshcop/border_agent_txt_data.cpp +++ b/src/core/meshcop/border_agent_txt_data.cpp @@ -57,22 +57,28 @@ const char TxtData::Key::kBbrSeqNum[] = "sq"; const char TxtData::Key::kBbrPort[] = "bb"; const char TxtData::Key::kOmrPrefix[] = "omr"; const char TxtData::Key::kExtAddress[] = "xa"; -#if OPENTHREAD_CONFIG_BORDER_AGENT_TXT_DATA_PARSER_ENABLE -const char TxtData::Key::kVendorName[] = "vn"; -const char TxtData::Key::kModelName[] = "mn"; -#endif +const char TxtData::Key::kVendorName[] = "vn"; +const char TxtData::Key::kModelName[] = "mn"; TxtData::TxtData(Instance &aInstance) : InstanceLocator(aInstance) #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE , mChangedTask(aInstance) #endif +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + , mShouldAddVendorName(true) + , mShouldAddVendorModel(true) +#endif { } #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE -Error TxtData::Prepare(uint8_t *aBuffer, uint16_t aBufferSize, uint16_t &aLength) +Error TxtData::Prepare(uint8_t *aBuffer, + uint16_t aBufferSize, + uint16_t &aLength, + bool aAddVendorName, + bool aAddVendorModel) { Error error = kErrorNone; Dns::TxtDataEncoder encoder(aBuffer, aBufferSize); @@ -152,6 +158,16 @@ Error TxtData::Prepare(uint8_t *aBuffer, uint16_t aBufferSize, uint16_t &aLength } #endif + if (aAddVendorName) + { + SuccessOrExit(error = encoder.AppendStringEntry(Key::kVendorName, Get().GetName())); + } + + if (aAddVendorModel) + { + SuccessOrExit(error = encoder.AppendStringEntry(Key::kModelName, Get().GetModel())); + } + aLength = encoder.GetLength(); exit: @@ -160,7 +176,8 @@ exit: Error TxtData::Prepare(ServiceTxtData &aTxtData) { - return Prepare(aTxtData.mData, sizeof(aTxtData.mData), aTxtData.mLength); + return Prepare(aTxtData.mData, sizeof(aTxtData.mData), aTxtData.mLength, /* aAddVendorName */ false, + /* aAddVendorModel */ false); } void TxtData::SetChangedCallback(ChangedCallback aCallback, void *aContext) @@ -194,18 +211,98 @@ exit: #if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE +void TxtData::PrepareWithVendorData(Heap::Data &aTxtData) +{ + uint16_t size; + uint16_t length; + uint8_t *buffer; + + // Allocate a large enough buffer to fit both the base TXT data + // generated by the Border Agent and the vendor extra TXT data. + // The vendor TXT data is appended at the end. If `mVendorData` + // does not contain vendor name or model entries, they are + // populated from `VendorInfo` and room is allocated for them. + + size = kMaxSizeNoVendorData + mVendorData.GetLength(); + + if (mShouldAddVendorName) + { + size += Tlv::kMaxVendorNameLength + sizeof(Key::kVendorName) + sizeof('='); + } + + if (mShouldAddVendorModel) + { + size += Tlv::kMaxVendorModelLength + sizeof(Key::kModelName) + sizeof('='); + } + + buffer = reinterpret_cast(Heap::CAlloc(size, sizeof(uint8_t))); + + OT_ASSERT(buffer != nullptr); + + SuccessOrAssert(Prepare(buffer, size, length, mShouldAddVendorName, mShouldAddVendorModel)); + + if (mVendorData.GetLength() != 0) + { + mVendorData.CopyBytesTo(buffer + length); + length += mVendorData.GetLength(); + } + + aTxtData.TakeFrom(buffer, length); +} + void TxtData::SetVendorData(const uint8_t *aVendorData, uint16_t aVendorDataLength) { + Dns::TxtEntry entry; + Dns::TxtEntry::Iterator iterator; + VerifyOrExit(!mVendorData.Matches(aVendorData, aVendorDataLength)); SuccessOrAssert(mVendorData.SetFrom(aVendorData, aVendorDataLength)); + + // Check whether the provided `mVendorData` contains vendor name + // or model entries. If not present, the values from `VendorInfo` + // will be used and encoded in the TXT data. + + mShouldAddVendorName = true; + mShouldAddVendorModel = true; + + iterator.Init(mVendorData.GetBytes(), mVendorData.GetLength()); + + while (iterator.GetNextEntry(entry) == kErrorNone) + { + if (entry.MatchesKey(Key::kVendorName)) + { + mShouldAddVendorName = false; + } + else if (entry.MatchesKey(Key::kModelName)) + { + mShouldAddVendorModel = false; + } + } + Refresh(); exit: return; } -#endif +void TxtData::HandleVendorNameChange(void) +{ + if (mShouldAddVendorName) + { + Refresh(); + } +} + +void TxtData::HandleVendorModelChange(void) +{ + if (mShouldAddVendorModel) + { + Refresh(); + } +} + +#endif // OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE uint32_t TxtData::StateBitmap::Determine(Instance &aInstance) { diff --git a/src/core/meshcop/border_agent_txt_data.hpp b/src/core/meshcop/border_agent_txt_data.hpp index 4f6cd7d5d..8794ae37f 100644 --- a/src/core/meshcop/border_agent_txt_data.hpp +++ b/src/core/meshcop/border_agent_txt_data.hpp @@ -51,6 +51,7 @@ #include "common/type_traits.hpp" #include "net/dns_types.hpp" #include "net/ip6_address.hpp" +#include "thread/vendor_info.hpp" namespace ot { namespace MeshCoP { @@ -61,6 +62,7 @@ namespace BorderAgent { class TxtData : public InstanceLocator { friend class ot::Notifier; + friend class ot::VendorInfo; public: typedef otBorderAgentConnMode ConnMode; ///< Connection Mode in a Border Agent State Bitmap. @@ -206,18 +208,6 @@ public: typedef otBorderAgentMeshCoPServiceTxtData ServiceTxtData; ///< Service TXT Data. typedef otBorderAgentMeshCoPServiceChangedCallback ChangedCallback; ///< Service TXT Data changed callback. - /** - * Prepares the MeshCoP service TXT data. - * - * @param[out] aBuffer A pointer to a buffer to store the TXT data. - * @param[in] aBufferSize The size of @p aBuffer. - * @param[out] aLength On exit, the length of the prepared TXT data. - * - * @retval kErrorNone Successfully prepared the TXT data. - * @retval kErrorNoBufs The @p aBufferSize is too small. - */ - Error Prepare(uint8_t *aBuffer, uint16_t aBufferSize, uint16_t &aLength); - /** * Prepares the MeshCoP service TXT data. * @@ -253,11 +243,15 @@ public: #if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE /** - * Returns the vendor TXT data. + * Prepares the MeshCoP service TXT data including the vendor extra TXT data. * - * @returns The vendor TXT data. + * The TXT data generated by the Border Agent itself is appended with the vendor extra TXT data (if any). + * If the vendor extra TXT data does not include vendor name or model entries, they are added automatically + * using the values from `VendorInfo`. + * + * @param[out] aTxtData A reference to a `Heap::Data` object to output the prepared TXT data. */ - const Heap::Data &GetVendorData(void) const { return mVendorData; } + void PrepareWithVendorData(Heap::Data &aTxtData); /** * Sets the vendor extra TXT data to be included when the Border Agent advertises the mDNS `_meshcop._udp` service. @@ -285,6 +279,8 @@ public: #endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE private: + static constexpr uint16_t kMaxSizeNoVendorData = OT_BORDER_AGENT_MESHCOP_SERVICE_TXT_DATA_MAX_LENGTH; + static const char kRecordVersion[]; struct Key @@ -302,10 +298,8 @@ private: static const char kBbrPort[]; static const char kOmrPrefix[]; static const char kExtAddress[]; -#if OPENTHREAD_CONFIG_BORDER_AGENT_TXT_DATA_PARSER_ENABLE static const char kVendorName[]; static const char kModelName[]; -#endif }; struct StateBitmap @@ -363,9 +357,14 @@ private: #endif }; + Error Prepare(uint8_t *aBuffer, uint16_t aBufferSize, uint16_t &aLength, bool aAddVendorName, bool aAddVendorModel); + #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE // Callback from Notifier void HandleNotifierEvents(Events aEvents); + // Callbacks from VendorInfo + void HandleVendorNameChange(void); + void HandleVendorModelChange(void); void HandleChangedTask(void); @@ -375,6 +374,8 @@ private: ChangedTask mChangedTask; #if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE Heap::Data mVendorData; + bool mShouldAddVendorName; + bool mShouldAddVendorModel; #endif #endif }; diff --git a/src/core/thread/vendor_info.cpp b/src/core/thread/vendor_info.cpp index 06f154eaf..35e69c275 100644 --- a/src/core/thread/vendor_info.cpp +++ b/src/core/thread/vendor_info.cpp @@ -69,7 +69,9 @@ VendorInfo::VendorInfo(Instance &aInstance) Error VendorInfo::SetName(const char *aName) { - Error error; + Error error = kErrorNone; + + VerifyOrExit(!StringMatch(mName, (aName == nullptr) ? "" : aName)); #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE VerifyOrExit(aName != nullptr && StringStartsWith(aName, kNamePrefix), error = kErrorInvalidArgs); @@ -77,11 +79,29 @@ Error VendorInfo::SetName(const char *aName) SuccessOrExit(error = StringCopy(mName, aName, kStringCheckUtf8Encoding)); +#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + Get().HandleVendorNameChange(); +#endif + exit: return error; } -Error VendorInfo::SetModel(const char *aModel) { return StringCopy(mModel, aModel, kStringCheckUtf8Encoding); } +Error VendorInfo::SetModel(const char *aModel) +{ + Error error = kErrorNone; + + VerifyOrExit(!StringMatch(mModel, (aModel == nullptr) ? "" : aModel)); + + SuccessOrExit(error = StringCopy(mModel, aModel, kStringCheckUtf8Encoding)); + +#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + Get().HandleVendorModelChange(); +#endif + +exit: + return error; +} Error VendorInfo::SetSwVersion(const char *aSwVersion) { diff --git a/tests/nexus/test_border_agent.cpp b/tests/nexus/test_border_agent.cpp index 03db80d0b..e44036661 100644 --- a/tests/nexus/test_border_agent.cpp +++ b/tests/nexus/test_border_agent.cpp @@ -1293,7 +1293,7 @@ struct TxtData uint16_t mLength; }; -void ValidateMeshCoPTxtData(TxtData &aTxtData, Node &aNode) +void ValidateMeshCoPTxtData(TxtData &aTxtData, Node &aNode, bool aExpectVendorInfo, const char *aVendorName = nullptr) { // State bitmap masks and field values static constexpr uint32_t kMaskConnectionMode = 7 << 0; @@ -1447,6 +1447,28 @@ void ValidateMeshCoPTxtData(TxtData &aTxtData, Node &aNode) VerifyOrQuit(!(stateBitmap & kFlagAdmitterSupported)); VerifyOrQuit(!info.mStateBitmap.mAdmitterSupported); } + + if (aExpectVendorInfo) + { + const char *expectedVendorName = (aVendorName != nullptr) ? aVendorName : aNode.Get().GetName(); + const char *expectedModelName = aNode.Get().GetModel(); + + aTxtData.ValidateKey("vn", expectedVendorName); + VerifyOrQuit(info.mHasVendorName); + VerifyOrQuit(StringMatch(info.mVendorName, expectedVendorName)); + + aTxtData.ValidateKey("mn", expectedModelName); + VerifyOrQuit(info.mHasModelName); + VerifyOrQuit(StringMatch(info.mModelName, expectedModelName)); + } + else + { + VerifyOrQuit(!aTxtData.ContainsKey("vn")); + VerifyOrQuit(!info.mHasVendorName); + + VerifyOrQuit(!aTxtData.ContainsKey("mn")); + VerifyOrQuit(!info.mHasModelName); + } } //---------------------------------------------------------------------------------------------------------------------- @@ -1466,7 +1488,7 @@ void ReadAndValidateMeshCoPTxtData(Node &aNode) SuccessOrQuit(aNode.Get().Prepare(serviceTxtData)); txtData.Init(serviceTxtData.mData, serviceTxtData.mLength); - ValidateMeshCoPTxtData(txtData, aNode); + ValidateMeshCoPTxtData(txtData, aNode, /* aExpectVendorInfo */ false); } void TestBorderAgentTxtDataCallback(void) @@ -1553,7 +1575,7 @@ void TestBorderAgentTxtDataCallback(void) static constexpr uint32_t kInfraIfIndex = 1; static constexpr uint16_t kMaxEntries = 5; -static constexpr uint16_t kMaxTxtDataSize = 128; +static constexpr uint16_t kMaxTxtDataSize = 400; typedef Dns::Name::Buffer DnsName; @@ -1626,12 +1648,14 @@ void HandleTxtCallback(otInstance *aInstance, const Dns::Multicast::Core::TxtRes outcome->mTtl = aResult->mTtl; } -void ValidateRegisteredServiceData(Dns::Multicast::Core::Service &aService, Node &aNode) +void ValidateRegisteredServiceData(Dns::Multicast::Core::Service &aService, + Node &aNode, + const char *aVendorName = nullptr) { TxtData txtData; txtData.Init(aService.mTxtData, aService.mTxtDataLength); - ValidateMeshCoPTxtData(txtData, aNode); + ValidateMeshCoPTxtData(txtData, aNode, /* aExpectVendorInfo */ true, aVendorName); } void TestBorderAgentServiceRegistration(void) @@ -1749,7 +1773,7 @@ void TestBorderAgentServiceRegistration(void) VerifyOrQuit(sTxtOutcomes.GetLength() == 1); VerifyOrQuit(sTxtOutcomes[0].mTtl > 0); txtData.Init(sTxtOutcomes[0].mTxtData, sTxtOutcomes[0].mTxtDataLength); - ValidateMeshCoPTxtData(txtData, node0); + ValidateMeshCoPTxtData(txtData, node0, /* aExptecVendorInfo */ true); sBrowseOutcomes.Clear(); sSrvOutcomes.Clear(); @@ -2086,11 +2110,10 @@ void TestBorderAgentServiceRegistration(void) VerifyOrQuit(service.mTtl > 0); VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex); VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED); - ValidateRegisteredServiceData(service, node0); + ValidateRegisteredServiceData(service, node0, /* aExpectedVendorName */ "nexus"); // Check that vendor TXT data is included at the end of // the registered service TXT data. - VerifyOrQuit(service.mTxtDataLength > txtDataLengthWithNoVendorData); VerifyOrQuit(service.mTxtDataLength > sizeof(kVendorTxtData)); VerifyOrQuit(!memcmp(&service.mTxtData[service.mTxtDataLength - sizeof(kVendorTxtData)], kVendorTxtData, sizeof(kVendorTxtData))); @@ -2131,6 +2154,68 @@ void TestBorderAgentServiceRegistration(void) VerifyOrQuit(node0.Get().GetNextService(*iterator, service, entryState) == kErrorNotFound); node0.Get().FreeIterator(*iterator); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Change vendor name and validate that the registered mDNS service is updated accordingly"); + + SuccessOrQuit(node0.Get().SetName("RD:v")); + nexus.AdvanceTime(Time::kOneSecondInMsec); + + iterator = node0.Get().AllocateIterator(); + VerifyOrQuit(iterator != nullptr); + + SuccessOrQuit(node0.Get().GetNextService(*iterator, service, entryState)); + Log(" HostName: %s", service.mHostName); + Log(" ServiceInstance: %s", service.mServiceInstance); + Log(" ServiceType: %s", service.mServiceType); + Log(" Port: %u", service.mPort); + Log(" TTL: %lu", ToUlong(service.mTtl)); + + VerifyOrQuit(StringMatch(service.mServiceType, "_meshcop._udp")); + VerifyOrQuit(StringStartsWith(service.mServiceInstance, "OpenThreadAgent")); + VerifyOrQuit(StringStartsWith(service.mHostName, "ot")); + VerifyOrQuit(service.mSubTypeLabelsLength == 0); + VerifyOrQuit(service.mPort == node0.Get().GetUdpPort()); + VerifyOrQuit(service.mTtl > 0); + VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex); + VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED); + ValidateRegisteredServiceData(service, node0); + + // Check that there is no more registered mDNS service + VerifyOrQuit(node0.Get().GetNextService(*iterator, service, entryState) == kErrorNotFound); + + node0.Get().FreeIterator(*iterator); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Change vendor model and validate that the registered mDNS service is updated accordingly"); + + SuccessOrQuit(node0.Get().SetModel("model")); + nexus.AdvanceTime(Time::kOneSecondInMsec); + + iterator = node0.Get().AllocateIterator(); + VerifyOrQuit(iterator != nullptr); + + SuccessOrQuit(node0.Get().GetNextService(*iterator, service, entryState)); + Log(" HostName: %s", service.mHostName); + Log(" ServiceInstance: %s", service.mServiceInstance); + Log(" ServiceType: %s", service.mServiceType); + Log(" Port: %u", service.mPort); + Log(" TTL: %lu", ToUlong(service.mTtl)); + + VerifyOrQuit(StringMatch(service.mServiceType, "_meshcop._udp")); + VerifyOrQuit(StringStartsWith(service.mServiceInstance, "OpenThreadAgent")); + VerifyOrQuit(StringStartsWith(service.mHostName, "ot")); + VerifyOrQuit(service.mSubTypeLabelsLength == 0); + VerifyOrQuit(service.mPort == node0.Get().GetUdpPort()); + VerifyOrQuit(service.mTtl > 0); + VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex); + VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED); + ValidateRegisteredServiceData(service, node0); + + // Check that there is no more registered mDNS service + VerifyOrQuit(node0.Get().GetNextService(*iterator, service, entryState) == kErrorNotFound); + + node0.Get().FreeIterator(*iterator); } } // namespace Nexus