From 1e79496c5728faa6ffa4b937d2fd42ed33c70a17 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Mon, 6 Apr 2026 21:47:39 -0700 Subject: [PATCH] [border-agent] handle mDNS service name conflict by renaming (#12790) This commit adds support for handling mDNS service name conflicts by automatically renaming the service when a collision is detected during registration. The new naming scheme appends a suffix based on the last two bytes of the device's Extended Address (e.g., " #AB1E"). If this name also conflicts, an additional index is appended (e.g., " #AB1E (1)"). Changes: - Added `mServiceRenameIndex` to `Manager` and `EphemeralKeyManager` to track re-naming attempts. - Updated `otBorderAgentSetMeshCoPServiceBaseName()` and CLI documentation to reflect the new naming and conflict resolution logic. - Updated `OT_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME_MAX_LENGTH` to ensure the full name fits within the 63-character DNS label limit. - Added Nexus tests to verify the renaming logic under conflict. --- include/openthread/border_agent.h | 10 +- include/openthread/instance.h | 2 +- src/cli/README.md | 2 +- src/core/common/string.cpp | 10 + src/core/common/string.hpp | 20 +- src/core/config/border_agent.h | 4 +- src/core/meshcop/border_agent.cpp | 67 ++++-- src/core/meshcop/border_agent.hpp | 9 +- .../meshcop/border_agent_ephemeral_key.cpp | 65 +++++- .../meshcop/border_agent_ephemeral_key.hpp | 10 +- tests/nexus/test_border_agent.cpp | 217 ++++++++++++++++++ 11 files changed, 382 insertions(+), 34 deletions(-) diff --git a/include/openthread/border_agent.h b/include/openthread/border_agent.h index 8f04e570c..fad95f6d0 100644 --- a/include/openthread/border_agent.h +++ b/include/openthread/border_agent.h @@ -258,10 +258,11 @@ otError otBorderAgentGetMeshCoPServiceTxtData(otInstance *aInstance, otBorderAge /** * Maximum string length of base name used in `otBorderAgentSetMeshCoPServiceBaseName()`. * - * The full DNS label is constructed by appending the Extended Address of the device (as 16-character hex digits) to + * To ensure name uniqueness and handle potential name conflicts, the OpenThread Border Agent module appends a + * suffix (e.g., " #XXXX" where "XXXX" represents the last two bytes of the device's Extended Address in hex) to * the given base name. */ -#define OT_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME_MAX_LENGTH (OT_DNS_MAX_LABEL_SIZE - 17) +#define OT_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME_MAX_LENGTH (OT_DNS_MAX_LABEL_SIZE - 13) /** * Sets the base name to construct the service instance name used when advertising the mDNS `_meshcop._udp` service by @@ -276,8 +277,9 @@ otError otBorderAgentGetMeshCoPServiceTxtData(otInstance *aInstance, otBorderAge * Per the Thread specification, the service instance should be a user-friendly name identifying the device model or * product. A recommended format is "VendorName ProductName". * - * To construct the full name and ensure name uniqueness, the OpenThread Border Agent module will append the Extended - * Address of the device (as 16-character hex digits) to the given base name. + * To construct the full name and ensure name uniqueness, the OpenThread Border Agent module appends a suffix + * (e.g., " #XXXX" where "XXXX" represents the last two bytes of the device's Extended Address in hex) to the given + * base name. If a name conflict is detected on the network, an additional index may be appended (e.g., " #XXXX (1)"). * * Note that the same name will be used for the ephemeral key service `_meshcop-e._udp` when the ephemeral key feature * is enabled and used. diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 666827fec..d32aaed89 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (588) +#define OPENTHREAD_API_VERSION (589) /** * @addtogroup api-instance diff --git a/src/cli/README.md b/src/cli/README.md index f4abcb549..c95f2ee01 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -421,7 +421,7 @@ Requires the `OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE` feature. The name can also be configured using the `OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME` configuration option (which is the recommended way to specify this name). This CLI command (and its corresponding API) is provided for projects where the name needs to be set after device initialization and at run-time. -Per the Thread specification, the service instance should be a user-friendly name identifying the device model or product. A recommended format is "VendorName ProductName". To construct the full name and ensure name uniqueness, the OpenThread Border Agent module will append the Extended Address of the device (as 16-character hex digits) to the given base name. Note that the same name will be used for the ephemeral key service `_meshcop-e._udp` when the ephemeral key feature is enabled and used. +Per the Thread specification, the service instance should be a user-friendly name identifying the device model or product. A recommended format is "VendorName ProductName". To construct the full name and ensure name uniqueness, the OpenThread Border Agent module appends a suffix (e.g., " #XXXX" where "XXXX" represents the last two bytes of the device's Extended Address in hex) to the given base name. If a name conflict is detected on the network, an additional index may be appended (e.g., " #XXXX (1)"). Note that the same name will be used for the ephemeral key service `_meshcop-e._udp` when the ephemeral key feature is enabled and used. ```bash ba servicebasename OpenThreadBorderAgent diff --git a/src/core/common/string.cpp b/src/core/common/string.cpp index b7f11de02..ce6175da9 100644 --- a/src/core/common/string.cpp +++ b/src/core/common/string.cpp @@ -357,6 +357,16 @@ StringWriter &StringWriter::AppendHexBytes(const uint8_t *aBytes, uint16_t aLeng return *this; } +StringWriter &StringWriter::AppendHexBytesUppercase(const uint8_t *aBytes, uint16_t aLength) +{ + while (aLength--) + { + Append("%02X", *aBytes++); + } + + return *this; +} + StringWriter &StringWriter::AppendCharMultipleTimes(char aChar, uint16_t aCount) { while (aCount--) diff --git a/src/core/common/string.hpp b/src/core/common/string.hpp index fe919b338..9740a31d5 100644 --- a/src/core/common/string.hpp +++ b/src/core/common/string.hpp @@ -170,6 +170,8 @@ bool StringMatch(const char *aFirstString, const char *aSecondString, StringMatc /** * Copies a string into a given target buffer with a given size if it fits. * + * Guarantees that @p aTargetBuffer remains unmodified if an error is returned. + * * @param[out] aTargetBuffer A pointer to the target buffer to copy into. * @param[out] aTargetSize The size (number of characters) in @p aTargetBuffer array. * @param[in] aSource A pointer to null-terminated string to copy from. Can be `nullptr` which treated as "". @@ -179,13 +181,15 @@ bool StringMatch(const char *aFirstString, const char *aSecondString, StringMatc * @retval kErrorInvalidArgs The @p aSource does not fit in the given buffer. * @retval kErrorParse The @p aSource does not follow the encoding format specified by @p aEncodingCheck. */ -Error StringCopy(char *TargetBuffer, uint16_t aTargetSize, const char *aSource, StringEncodingCheck aEncodingCheck); +Error StringCopy(char *aTargetBuffer, uint16_t aTargetSize, const char *aSource, StringEncodingCheck aEncodingCheck); /** * Copies a string into a given target buffer with a given size if it fits. * * @tparam kSize The size of buffer. * + * Guarantees that @p aTargetBuffer remains unmodified if an error is returned. + * * @param[out] aTargetBuffer A reference to the target buffer array to copy into. * @param[in] aSource A pointer to null-terminated string to copy from. Can be `nullptr` which treated as "". * @param[in] aEncodingCheck Specifies the encoding format check (e.g., UTF-8) to perform. @@ -483,15 +487,25 @@ public: StringWriter &AppendVarArgs(const char *aFormat, va_list aArgs) OT_TOOL_PRINTF_STYLE_FORMAT_ARG_CHECK(2, 0); /** - * Appends an array of bytes in hex representation (using "%02x" style) to the buffer. + * Appends an array of bytes in hex representation (using lowercase "%02x" style) to the buffer. * * @param[in] aBytes A pointer to buffer containing the bytes to append. * @param[in] aLength The length of @p aBytes buffer (in bytes). * - * @returns The string writer. + * @returns A reference to this string writer. */ StringWriter &AppendHexBytes(const uint8_t *aBytes, uint16_t aLength); + /** + * Appends an array of bytes in hex representation (using uppercase "%02X" style) to the buffer. + * + * @param[in] aBytes A pointer to buffer containing the bytes to append. + * @param[in] aLength The length of @p aBytes buffer (in bytes). + * + * @returns A reference to this string writer. + */ + StringWriter &AppendHexBytesUppercase(const uint8_t *aBytes, uint16_t aLength); + /** * Appends a given character a given number of times. * diff --git a/src/core/config/border_agent.h b/src/core/config/border_agent.h index bbeffc914..6a60db01c 100644 --- a/src/core/config/border_agent.h +++ b/src/core/config/border_agent.h @@ -116,10 +116,10 @@ * Per the Thread specification, the service instance should be a user-friendly name identifying the device model or * product. A recommended format is "VendorName ProductName". * - * The name MUST have a length less than or equal to `OT_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME_MAX_LENGTH` (47 chars). + * The name MUST have a length less than or equal to `OT_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME_MAX_LENGTH`. */ #ifndef OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME -#define OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME "OpenThread BR (unspecified vendor) " +#define OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME "OpenThread BR (unspecified vendor)" #endif /** diff --git a/src/core/meshcop/border_agent.cpp b/src/core/meshcop/border_agent.cpp index dc55253a7..e0c800323 100644 --- a/src/core/meshcop/border_agent.cpp +++ b/src/core/meshcop/border_agent.cpp @@ -70,6 +70,9 @@ Manager::Manager(Instance &aInstance) #if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE , mIdInitialized(false) #endif +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + , mServiceRenameIndex(0) +#endif { mCommissionerAloc.InitAsThreadOriginMeshLocal(); @@ -80,6 +83,8 @@ Manager::Manager(Instance &aInstance) static_assert(sizeof(kDefaultBaseServiceName) - 1 <= kBaseServiceNameMaxLen, "OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME is too long"); + + SuccessOrAssert(StringCopy(mBaseServiceName, kDefaultBaseServiceName)); #endif } @@ -394,18 +399,15 @@ exit: Error Manager::SetServiceBaseName(const char *aBaseName) { - Error error = kErrorNone; - Dns::Name::LabelBuffer newName; + Error error = kErrorNone; - VerifyOrExit(StringLength(aBaseName, kBaseServiceNameMaxLen + 1) <= kBaseServiceNameMaxLen, - error = kErrorInvalidArgs); + VerifyOrExit(!StringMatch(mBaseServiceName, aBaseName)); - ConstructServiceName(aBaseName, newName); - - VerifyOrExit(!StringMatch(newName, mServiceName)); + SuccessOrExit(error = StringCopy(mBaseServiceName, aBaseName)); + mServiceRenameIndex = 0; UnregisterService(); - IgnoreError(StringCopy(mServiceName, newName)); + ConstructServiceName(); RegisterService(); exit: @@ -416,17 +418,28 @@ const char *Manager::GetServiceName(void) { if (IsServiceNameEmpty()) { - ConstructServiceName(kDefaultBaseServiceName, mServiceName); + ConstructServiceName(); } return mServiceName; } -void Manager::ConstructServiceName(const char *aBaseName, Dns::Name::LabelBuffer &aNameBuffer) -{ - StringWriter writer(aNameBuffer, sizeof(Dns::Name::LabelBuffer)); +void Manager::ConstructServiceName(void) { ConstructServiceName(mServiceRenameIndex, mServiceName); } - writer.Append("%.*s%s", kBaseServiceNameMaxLen, aBaseName, Get().GetExtAddress().ToString().AsCString()); +void Manager::ConstructServiceName(uint16_t aRenameIndex, Dns::Name::LabelBuffer &aNameBuffer) +{ + static constexpr uint8_t kExtAddrSuffixLength = 2; + + StringWriter writer(aNameBuffer, sizeof(Dns::Name::LabelBuffer)); + const Mac::ExtAddress &extAddr = Get().GetExtAddress(); + + writer.Append("%.*s #", kBaseServiceNameMaxLen, mBaseServiceName); + writer.AppendHexBytesUppercase(GetArrayEnd(extAddr.m8) - kExtAddrSuffixLength, kExtAddrSuffixLength); + + if (aRenameIndex != 0) + { + writer.Append(" (%u)", aRenameIndex % 1000); + } } void Manager::RegisterService(void) @@ -454,7 +467,8 @@ void Manager::RegisterService(void) } #endif - Get().RegisterService(service, /* aRequestId */ 0, /* aCallback */ nullptr); + LogInfo("Registering service %s %s", service.mServiceInstance, kServiceType); + Get().RegisterService(service, /* aRequestId */ 0, HandleRegisterDone); exit: return; @@ -471,12 +485,37 @@ void Manager::UnregisterService(void) service.mServiceInstance = GetServiceName(); service.mServiceType = kServiceType; + LogInfo("Unregistering service %s %s", service.mServiceInstance, kServiceType); + Get().UnregisterService(service, /* aRequestId */ 0, /* aCallback */ nullptr); exit: return; } +void Manager::HandleRegisterDone(otInstance *aInstance, otPlatDnssdRequestId aRequestId, otError aError) +{ + OT_UNUSED_VARIABLE(aRequestId); + AsCoreType(aInstance).Get().HandleRegisterDone(aError); +} + +void Manager::HandleRegisterDone(Error aError) +{ + VerifyOrExit(aError == kErrorDuplicated); + + LogInfoOnError(aError, "register service %s %s - retrying with a new name", GetServiceName(), kServiceType); + + UnregisterService(); + + mServiceRenameIndex++; + ConstructServiceName(); + + RegisterService(); + +exit: + return; +} + #endif // OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE #if OPENTHREAD_CONFIG_BORDER_AGENT_COMMISSIONER_EVICTION_API_ENABLE diff --git a/src/core/meshcop/border_agent.hpp b/src/core/meshcop/border_agent.hpp index c99b6767b..eb3aa6006 100644 --- a/src/core/meshcop/border_agent.hpp +++ b/src/core/meshcop/border_agent.hpp @@ -408,9 +408,13 @@ private: const char *GetServiceName(void); bool IsServiceNameEmpty(void) const { return mServiceName[0] == kNullChar; } - void ConstructServiceName(const char *aBaseName, Dns::Name::LabelBuffer &aNameBuffer); + void ConstructServiceName(void); + void ConstructServiceName(uint16_t aRenameIndex, Dns::Name::LabelBuffer &aNameBuffer); void RegisterService(void); void UnregisterService(void); + void HandleRegisterDone(Error aError); + + static void HandleRegisterDone(otInstance *aInstance, otPlatDnssdRequestId aRequestId, otError aError); #endif #if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE @@ -435,7 +439,10 @@ private: bool mIdInitialized; #endif #if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + + char mBaseServiceName[kBaseServiceNameMaxLen + 1]; Dns::Name::LabelBuffer mServiceName; + uint16_t mServiceRenameIndex; #endif Counters mCounters; }; diff --git a/src/core/meshcop/border_agent_ephemeral_key.cpp b/src/core/meshcop/border_agent_ephemeral_key.cpp index 760252732..9d4995f71 100644 --- a/src/core/meshcop/border_agent_ephemeral_key.cpp +++ b/src/core/meshcop/border_agent_ephemeral_key.cpp @@ -59,7 +59,13 @@ EphemeralKeyManager::EphemeralKeyManager(Instance &aInstance) , mCoapDtlsSession(nullptr) , mTimer(aInstance) , mCallbackTask(aInstance) +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + , mServiceRenameIndex(0) +#endif { +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + ClearAllBytes(mServiceName); +#endif } void EphemeralKeyManager::SetEnabled(bool aEnabled) @@ -215,7 +221,8 @@ void EphemeralKeyManager::UpdateCountersAndRecordEvent(DeactivationReason aReaso void EphemeralKeyManager::SetState(State aState) { #if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE - bool isServiceRegistered = ShouldRegisterService(); + bool wasServiceRegistered = ShouldRegisterService(); + bool shouldRegister; #endif VerifyOrExit(mState != aState); @@ -224,8 +231,22 @@ void EphemeralKeyManager::SetState(State aState) mCallbackTask.Post(); #if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE - VerifyOrExit(isServiceRegistered != ShouldRegisterService()); - RegisterOrUnregisterService(); + + shouldRegister = ShouldRegisterService(); + VerifyOrExit(wasServiceRegistered != shouldRegister); + + if (shouldRegister) + { + // We start with the same service name as used by the `Manager`. + // We keep track of it separately, so it can be renamed on its + // own. + + mServiceRenameIndex = Get().mServiceRenameIndex; + Get().ConstructServiceName(mServiceRenameIndex, mServiceName); + } + + RegisterOrUnregisterService(shouldRegister); + #endif exit: @@ -349,20 +370,27 @@ bool EphemeralKeyManager::ShouldRegisterService(void) const return shouldRegister; } -void EphemeralKeyManager::RegisterOrUnregisterService(void) +void EphemeralKeyManager::ConstructServiceName(void) +{ + Get().ConstructServiceName(mServiceRenameIndex, mServiceName); +} + +void EphemeralKeyManager::RegisterOrUnregisterService(bool aRegister) { Dnssd::Service service; VerifyOrExit(Get().IsReady()); service.Clear(); - service.mServiceInstance = Get().GetServiceName(); + service.mServiceInstance = mServiceName; service.mServiceType = kServiceType; service.mPort = GetUdpPort(); - if (ShouldRegisterService()) + LogInfo("%segistering service %s %s", aRegister ? "R" : "Unr", mServiceName, kServiceType); + + if (aRegister) { - Get().RegisterService(service, /* aRequestId */ 0, /* aCallback */ nullptr); + Get().RegisterService(service, /* aRequestId */ 0, HandleRegisterDone); } else { @@ -373,6 +401,29 @@ exit: return; } +void EphemeralKeyManager::HandleRegisterDone(otInstance *aInstance, otPlatDnssdRequestId aRequestId, otError aError) +{ + OT_UNUSED_VARIABLE(aRequestId); + AsCoreType(aInstance).Get().HandleRegisterDone(aError); +} + +void EphemeralKeyManager::HandleRegisterDone(Error aError) +{ + VerifyOrExit(aError == kErrorDuplicated); + + LogInfoOnError(aError, "register service %s %s - retrying with a new name", mServiceName, kServiceType); + + RegisterOrUnregisterService(/* aRegister */ false); + + mServiceRenameIndex++; + ConstructServiceName(); + + RegisterOrUnregisterService(/* aRegister */ true); + +exit: + return; +} + #endif // OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE const char *EphemeralKeyManager::StateToString(State aState) diff --git a/src/core/meshcop/border_agent_ephemeral_key.hpp b/src/core/meshcop/border_agent_ephemeral_key.hpp index 057a1f535..e2a168c0e 100644 --- a/src/core/meshcop/border_agent_ephemeral_key.hpp +++ b/src/core/meshcop/border_agent_ephemeral_key.hpp @@ -229,7 +229,11 @@ private: void UpdateCountersAndRecordEvent(DeactivationReason aReason); #if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE bool ShouldRegisterService(void) const; - void RegisterOrUnregisterService(void); + void ConstructServiceName(void); + void RegisterOrUnregisterService(bool aRegister); + void HandleRegisterDone(Error aError); + + static void HandleRegisterDone(otInstance *aInstance, otPlatDnssdRequestId aRequestId, otError aError); #endif // Session or Transport callbacks @@ -257,6 +261,10 @@ private: TimeoutTimer mTimer; CallbackTask mCallbackTask; Callback mCallback; +#if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + Dns::Name::LabelBuffer mServiceName; + uint16_t mServiceRenameIndex; +#endif }; } // namespace BorderAgent diff --git a/tests/nexus/test_border_agent.cpp b/tests/nexus/test_border_agent.cpp index 6a47f7876..68d91ff36 100644 --- a/tests/nexus/test_border_agent.cpp +++ b/tests/nexus/test_border_agent.cpp @@ -2350,6 +2350,222 @@ void TestBorderAgentServiceRegistration(void) node0.Get().FreeIterator(*iterator); } +void TestBorderAgentServiceRegistrationRename(void) +{ + static const char kServiceBaseName[] = "VeryLongServiceBaseNameForTestingPurposeOfLimitsMax"; + static const char kEphemeralKey[] = "nexus1234"; + + static constexpr uint32_t kUdpPort = 49155; + + Core nexus; + Node &node0 = nexus.CreateNode(); + Node &node1 = nexus.CreateNode(); + Mac::ExtAddress extAddress; + Dns::Multicast::Core::Iterator *iterator; + Dns::Multicast::Core::Service service; + Dns::Multicast::Core::EntryState entryState; + String expectedName; + bool foundService; + bool foundEpskService; + + Log("------------------------------------------------------------------------------------------------------"); + Log("TestBorderAgentServiceRegistrationRename"); + + nexus.AdvanceTime(0); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Set the same Ext address on both node0 and node1 + + extAddress.GenerateRandom(); + extAddress.m8[6] = 0xbe; + extAddress.m8[7] = 0xef; + + node0.Get().SetExtAddress(extAddress); + node1.Get().SetExtAddress(extAddress); + VerifyOrQuit(node1.Get().GetExtAddress() == node0.Get().GetExtAddress()); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Set the Service Base Name on both node0 and node1 + SuccessOrQuit(node0.Get().SetServiceBaseName(kServiceBaseName)); + SuccessOrQuit(node1.Get().SetServiceBaseName(kServiceBaseName)); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Disable Border Agent function on node1 + node1.Get().SetEnabled(false); + + node0.Form(); + node1.Form(); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Construct the expected service instance name label. + + expectedName.Append("%s #%02X%02X", kServiceBaseName, extAddress.m8[6], extAddress.m8[7]); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Enable mDNS + SuccessOrQuit(node0.Get().SetEnabled(true, kInfraIfIndex)); + VerifyOrQuit(node0.Get().IsEnabled()); + SuccessOrQuit(node1.Get().SetEnabled(true, kInfraIfIndex)); + VerifyOrQuit(node1.Get().IsEnabled()); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + nexus.AdvanceTime(50 * Time::kOneSecondInMsec); + VerifyOrQuit(node0.Get().IsLeader()); + VerifyOrQuit(node1.Get().IsLeader()); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + VerifyOrQuit(node0.Get().IsEnabled()); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Validate the registered mDNS MeshCop service by Border Agent"); + + iterator = node0.Get().AllocateIterator(); + VerifyOrQuit(iterator != nullptr); + + foundService = false; + + while (node0.Get().GetNextService(*iterator, service, entryState) == kErrorNone) + { + Log("- - - - - - - - - - - - - - - - -"); + 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)); + + if (StringMatch(service.mServiceType, "_meshcop._udp")) + { + VerifyOrQuit(StringMatch(service.mServiceInstance, expectedName.AsCString())); + VerifyOrQuit(StringStartsWith(service.mHostName, "ot")); + VerifyOrQuit(service.mPort == node0.Get().GetUdpPort()); + VerifyOrQuit(service.mSubTypeLabelsLength == 0); + VerifyOrQuit(service.mTtl > 0); + VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex); + VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED); + ValidateRegisteredServiceData(service, node0); + foundService = true; + } + } + + VerifyOrQuit(foundService); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Enable BorderAgent on node1"); + + node1.Get().SetEnabled(true); + + nexus.AdvanceTime(2 * Time::kOneSecondInMsec); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Validate the registered mDNS MeshCop service by Border Agent on node1"); + + iterator = node1.Get().AllocateIterator(); + VerifyOrQuit(iterator != nullptr); + + foundService = false; + + while (node1.Get().GetNextService(*iterator, service, entryState) == kErrorNone) + { + Log("- - - - - - - - - - - - - - - - -"); + 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)); + + if (StringMatch(service.mServiceType, "_meshcop._udp")) + { + Log("Validate that node1 properly renamed the conflicted service name"); + + VerifyOrQuit(StringStartsWith(service.mServiceInstance, expectedName.AsCString())); + expectedName.Append(" (1)"); + VerifyOrQuit(StringMatch(service.mServiceInstance, expectedName.AsCString())); + + VerifyOrQuit(StringStartsWith(service.mHostName, "ot")); + VerifyOrQuit(service.mSubTypeLabelsLength == 0); + VerifyOrQuit(service.mTtl > 0); + VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex); + VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED); + ValidateRegisteredServiceData(service, node1); + foundService = true; + } + } + + VerifyOrQuit(foundService); + + node1.Get().FreeIterator(*iterator); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Enable and start ephemeral key on node 1"); + + node1.Get().SetEnabled(true); + VerifyOrQuit(node1.Get().GetState() == EphemeralKeyManager::kStateStopped); + node1.Get().SetCallback(HandleEphemeralKeyChange, &node1); + + SuccessOrQuit(node1.Get().Start(kEphemeralKey, /* aTimeout */ 0, kUdpPort)); + + nexus.AdvanceTime(10 * Time::kOneSecondInMsec); + + VerifyOrQuit(node1.Get().GetState() == EphemeralKeyManager::kStateStarted); + VerifyOrQuit(node1.Get().GetUdpPort() == kUdpPort); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check the registered services - validate the same service name is used for `_meshcop-e` service"); + + iterator = node1.Get().AllocateIterator(); + VerifyOrQuit(iterator != nullptr); + + foundService = false; + foundEpskService = false; + + while (node1.Get().GetNextService(*iterator, service, entryState) == kErrorNone) + { + Log("- - - - - - - - - - - - - - - - -"); + 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)); + + if (StringMatch(service.mServiceType, "_meshcop._udp")) + { + VerifyOrQuit(StringMatch(service.mServiceInstance, expectedName.AsCString())); + + VerifyOrQuit(service.mPort == node1.Get().GetUdpPort()); + ValidateRegisteredServiceData(service, node1); + + VerifyOrQuit(StringStartsWith(service.mHostName, "ot")); + VerifyOrQuit(service.mSubTypeLabelsLength == 0); + VerifyOrQuit(service.mTtl > 0); + VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex); + VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED); + foundService = true; + } + else if (StringMatch(service.mServiceType, "_meshcop-e._udp")) + { + VerifyOrQuit(StringMatch(service.mServiceInstance, expectedName.AsCString())); + + VerifyOrQuit(service.mPort == kUdpPort); + VerifyOrQuit(service.mTxtDataLength == 1); + VerifyOrQuit(service.mTxtData[0] == 0); + + VerifyOrQuit(StringStartsWith(service.mHostName, "ot")); + VerifyOrQuit(service.mSubTypeLabelsLength == 0); + VerifyOrQuit(service.mTtl > 0); + VerifyOrQuit(service.mInfraIfIndex == kInfraIfIndex); + VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED); + foundEpskService = true; + } + } + + VerifyOrQuit(foundService); + VerifyOrQuit(foundEpskService); + + node1.Get().FreeIterator(*iterator); +} + } // namespace Nexus } // namespace ot @@ -2361,6 +2577,7 @@ int main(void) ot::Nexus::TestHistoryTrackerBorderAgentEpskcEvent(); ot::Nexus::TestBorderAgentTxtDataCallback(); ot::Nexus::TestBorderAgentServiceRegistration(); + ot::Nexus::TestBorderAgentServiceRegistrationRename(); printf("All tests passed\n"); return 0; }