From 36a8f71d0693bfb1407fc48444c4da6e7f2d32d6 Mon Sep 17 00:00:00 2001 From: Li Cao Date: Wed, 19 Feb 2025 05:33:16 +0800 Subject: [PATCH] [border-agent] add new apis for refactoring meshcop service publishing (#11215) This commit adds a new OT API to set callback for border agent state change: `otBorderAgentSetMeshCoPServiceChangedCallback` `otBorderAgentStateChangedCallback` is invoked when the 'IsActive' state of BorderAgent changes or the MeshCoP TXT data from Thread stack changes. For example, the network name, the EXT PAN ID. This is used to update the MeshCoP TXT data that will be published on the host and notify the platform to start/stop the UDP Proxy. After applying the UdpProxy design, the UDP port of published MeshCoP service will be provided by the UdpProxy on the host (in ot-br-posix) instead of OT core. --- include/openthread/border_agent.h | 29 +++ include/openthread/instance.h | 2 +- src/core/api/border_agent_api.cpp | 7 + src/core/border_router/routing_manager.cpp | 3 + src/core/meshcop/border_agent.cpp | 198 +++++++++++++++++++++ src/core/meshcop/border_agent.hpp | 136 ++++++++++++++ src/core/net/dns_types.hpp | 12 +- tests/nexus/test_border_agent.cpp | 164 ++++++++++++++++- 8 files changed, 547 insertions(+), 4 deletions(-) diff --git a/include/openthread/border_agent.h b/include/openthread/border_agent.h index eada91deb..c91292fb5 100644 --- a/include/openthread/border_agent.h +++ b/include/openthread/border_agent.h @@ -143,6 +143,35 @@ bool otBorderAgentIsActive(otInstance *aInstance); */ uint16_t otBorderAgentGetUdpPort(otInstance *aInstance); +/** + * This callback informs the application of the changes in the state of the MeshCoP service. + * + * In specific, the 'state' includes the MeshCoP TXT data originated from the Thread network and whether the + * Border Agent is Active (which can be obtained by `otBorderAgentIsActive`). + * + * @param[in] aTxtData A pointer to the encoded MeshCoP TXT data originated from the Thread network. + * @param[in] aLength The length of the encoded TXT data. + * @param[in] aContext A pointer to application-specific context. + */ +typedef void (*otBorderAgentMeshCoPServiceChangedCallback)(const uint8_t *aTxtData, uint16_t aLength, void *aContext); + +/** + * Sets the callback function used by the Border Agent to notify of any changes to the state of the MeshCoP service. + * + * The callback is invoked when the 'Is Active' state of the Border Agent or the MeshCoP service TXT data values + * change. For example, it is invoked when the network name or the extended PAN ID changes and passes the updated + * encoded TXT data to the application layer. + * + * This callback is invoked once right after this API is called to provide initial states of the MeshCoP service. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aCallback The callback to be invoked when there are any changes of the MeshCoP service. + * @param[in] aContext A pointer to application-specific context. + */ +void otBorderAgentSetMeshCoPServiceChangedCallback(otInstance *aInstance, + otBorderAgentMeshCoPServiceChangedCallback aCallback, + void *aContext); + /** * Gets the randomly generated Border Agent ID. * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 234ee3428..7749aac7b 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 (477) +#define OPENTHREAD_API_VERSION (478) /** * @addtogroup api-instance diff --git a/src/core/api/border_agent_api.cpp b/src/core/api/border_agent_api.cpp index d0da6b536..f19c0433c 100644 --- a/src/core/api/border_agent_api.cpp +++ b/src/core/api/border_agent_api.cpp @@ -76,6 +76,13 @@ otError otBorderAgentGetNextSessionInfo(otBorderAgentSessionIterator *aIterator, return AsCoreType(aIterator).GetNextSessionInfo(*aSessionInfo); } +void otBorderAgentSetMeshCoPServiceChangedCallback(otInstance *aInstance, + otBorderAgentMeshCoPServiceChangedCallback aCallback, + void *aContext) +{ + AsCoreType(aInstance).Get().SetMeshCoPServiceChangedCallback(aCallback, aContext); +} + const otBorderAgentCounters *otBorderAgentGetCounters(otInstance *aInstance) { return &AsCoreType(aInstance).Get().GetCounters(); diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index eaac06679..5b80aec20 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -2301,6 +2301,9 @@ void RoutingManager::OmrPrefixManager::SetFavordPrefix(const OmrPrefix &aOmrPref if (oldFavoredPrefix != mFavoredPrefix) { +#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE + Get().PostNotifyMeshCoPServiceChangedTask(); +#endif LogInfo("Favored OMR prefix: %s -> %s", FavoredToString(oldFavoredPrefix).AsCString(), FavoredToString(mFavoredPrefix).AsCString()); } diff --git a/src/core/meshcop/border_agent.cpp b/src/core/meshcop/border_agent.cpp index ca3add050..8e711baf9 100644 --- a/src/core/meshcop/border_agent.cpp +++ b/src/core/meshcop/border_agent.cpp @@ -52,6 +52,7 @@ BorderAgent::BorderAgent(Instance &aInstance) #if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE , mIdInitialized(false) #endif + , mNotifyMeshCoPServiceChangedTask(aInstance) #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE , mEphemeralKeyManager(aInstance) #endif @@ -114,6 +115,7 @@ void BorderAgent::Start(void) pskc.Clear(); mIsRunning = true; + PostNotifyMeshCoPServiceChangedTask(); LogInfo("Border Agent start listening on port %u", GetUdpPort()); @@ -132,6 +134,7 @@ void BorderAgent::Stop(void) mDtlsTransport.Close(); mIsRunning = false; + PostNotifyMeshCoPServiceChangedTask(); LogInfo("Border Agent stopped"); @@ -141,6 +144,13 @@ exit: uint16_t BorderAgent::GetUdpPort(void) const { return mDtlsTransport.GetUdpPort(); } +void BorderAgent::SetMeshCoPServiceChangedCallback(MeshCoPServiceChangedCallback aCallback, void *aContext) +{ + mMeshCoPServiceChangedCallback.Set(aCallback, aContext); + + mNotifyMeshCoPServiceChangedTask.Post(); +} + void BorderAgent::HandleNotifierEvents(Events aEvents) { if (aEvents.Contains(kEventThreadRoleChanged)) @@ -169,6 +179,12 @@ void BorderAgent::HandleNotifierEvents(Events aEvents) pskc.Clear(); } + if (aEvents.ContainsAny(kEventThreadRoleChanged | kEventThreadExtPanIdChanged | kEventThreadNetworkNameChanged | + kEventThreadBackboneRouterStateChanged | kEventActiveDatasetChanged)) + { + PostNotifyMeshCoPServiceChangedTask(); + } + exit: return; } @@ -316,6 +332,188 @@ exit: FreeMessageOnError(message, error); } +void BorderAgent::NotifyMeshCoPServiceChanged(void) +{ + MeshCoPTxtEncoder meshCoPTxtEncoder(GetInstance()); + + VerifyOrExit(mMeshCoPServiceChangedCallback.IsSet()); + SuccessOrAssert(meshCoPTxtEncoder.EncodeTxtData()); + + mMeshCoPServiceChangedCallback.Invoke(meshCoPTxtEncoder.GetTxtData(), meshCoPTxtEncoder.GetTxtDataLen()); + +exit: + return; +} + +void BorderAgent::PostNotifyMeshCoPServiceChangedTask(void) +{ + if (mMeshCoPServiceChangedCallback.IsSet()) + { + mNotifyMeshCoPServiceChangedTask.Post(); + } +} + +//---------------------------------------------------------------------------------------------------------------------- +// BorderAgent::MeshCoPTxtEncoder + +Error BorderAgent::MeshCoPTxtEncoder::AppendTxtEntry(const char *aKey, const void *aValue, uint16_t aValueLength) +{ + Dns::TxtEntry txtEntry; + + txtEntry.Init(aKey, reinterpret_cast(aValue), aValueLength); + return txtEntry.AppendTo(mAppender); +} + +template <> Error BorderAgent::MeshCoPTxtEncoder::AppendTxtEntry(const char *aKey, const NameData &aObject) +{ + return AppendTxtEntry(aKey, aObject.GetBuffer(), aObject.GetLength()); +} + +Error BorderAgent::MeshCoPTxtEncoder::EncodeTxtData(void) +{ +#if OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_1 + static const char kThreadVersionString[] = "1.1.1"; +#elif OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_2 + static const char kThreadVersionString[] = "1.2.0"; +#elif OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_3 + static const char kThreadVersionString[] = "1.3.0"; +#elif OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_3_1 + static const char kThreadVersionString[] = "1.3.1"; +#elif OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_4 + static const char kThreadVersionString[] = "1.4.0"; +#endif + + Error error = kErrorNone; +#if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE + Id id; +#endif + StateBitmap state; + +#if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE + if (Get().GetId(id) == kErrorNone) + { + SuccessOrExit(error = AppendTxtEntry("id", id)); + } +#endif + SuccessOrExit(error = AppendTxtEntry("nn", Get().GetNetworkName().GetAsData())); + SuccessOrExit(error = AppendTxtEntry("xp", Get().GetExtPanId())); + SuccessOrExit(error = AppendTxtEntry("tv", NameData(kThreadVersionString, sizeof(kThreadVersionString) - 1))); + SuccessOrExit(error = AppendTxtEntry("xa", Get().GetExtAddress())); + + state = GetStateBitmap(); + SuccessOrExit(error = AppendTxtEntry("sb", BigEndian::HostSwap32(state.ToUint32()))); + + if (state.mThreadIfStatus == kThreadIfStatusActive) + { + SuccessOrExit(error = AppendTxtEntry( + "pt", BigEndian::HostSwap32(Get().GetLeaderData().GetPartitionId()))); + if (Get().GetTimestamp().IsValid()) + { + SuccessOrExit(error = AppendTxtEntry("at", Get().GetTimestamp())); + } + } + +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + SuccessOrExit(error = AppendBbrTxtEntry(state)); +#endif +#if OTBR_ENABLE_BORDER_ROUTING + SuccessOrExit(error = AppendOmrTxtEntry()); +#endif + +exit: + return error; +} + +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE +Error BorderAgent::MeshCoPTxtEncoder::AppendBbrTxtEntry(StateBitmap aState) +{ + Error error = kErrorNone; + const DomainName &domainName = Get().GetDomainName(); + + if (aState.mBbrIsActive) + { + BackboneRouter::Config bbrConfig; + + Get().GetConfig(bbrConfig); + SuccessOrExit(error = AppendTxtEntry("sq", bbrConfig.mSequenceNumber)); + SuccessOrExit(error = AppendTxtEntry("bb", BigEndian::HostSwap16(BackboneRouter::kBackboneUdpPort))); + } + + error = AppendTxtEntry( + "dn", NameData(domainName.GetAsCString(), StringLength(domainName.GetAsCString(), sizeof(domainName)))); + +exit: + return error; +} +#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +Error BorderAgent::MeshCoPTxtEncoder::AppendOmrTxtEntry(void) +{ + Error error = kErrorNone; + Ip6::Prefix prefix; + BorderRouter::RoutingManager::RoutePreference preference; + + if ((error = Get().GetFavoredOmrPrefix(prefix, preference)) == kErrorNone) + { + uint8_t omrData[Ip6::NetworkPrefix::kSize + 1]; + omrData[0] = prefix.GetLength(); + memcpy(omrData + 1, prefix.GetBytes(), prefix.GetBytesSize()); + + SuccessOrExit(error = AppendTxtEntry("omr", omrData)); + } + +exit: + return error; +} +#endif + +BorderAgent::MeshCoPTxtEncoder::StateBitmap BorderAgent::MeshCoPTxtEncoder::GetStateBitmap(void) +{ + StateBitmap state; + + state.mConnectionMode = kConnectionModePskc; + state.mAvailability = kAvailabilityHigh; + + switch (Get().GetRole()) + { + case Mle::DeviceRole::kRoleDisabled: + state.mThreadIfStatus = kThreadIfStatusNotInitialized; + state.mThreadRole = kThreadRoleDisabledOrDetached; + break; + case Mle::DeviceRole::kRoleDetached: + state.mThreadIfStatus = kThreadIfStatusInitialized; + state.mThreadRole = kThreadRoleDisabledOrDetached; + break; + case Mle::DeviceRole::kRoleChild: + state.mThreadIfStatus = kThreadIfStatusActive; + state.mThreadRole = kThreadRoleChild; + break; + case Mle::DeviceRole::kRoleRouter: + state.mThreadIfStatus = kThreadIfStatusActive; + state.mThreadRole = kThreadRoleRouter; + break; + case Mle::DeviceRole::kRoleLeader: + state.mThreadIfStatus = kThreadIfStatusActive; + state.mThreadRole = kThreadRoleLeader; + break; + } + +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + state.mBbrIsActive = state.mThreadIfStatus == kThreadIfStatusActive && + Get().GetState() != BackboneRouter::Local::State::kStateDisabled; + state.mBbrIsPrimary = state.mThreadIfStatus == kThreadIfStatusActive && + Get().GetState() == BackboneRouter::Local::State::kStatePrimary; +#endif + +#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE + state.mEpskcSupported = + Get().GetState() != EphemeralKeyManager::State::kStateDisabled; +#endif + + return state; +} + //---------------------------------------------------------------------------------------------------------------------- // BorderAgent::SessionIterator diff --git a/src/core/meshcop/border_agent.hpp b/src/core/meshcop/border_agent.hpp index 357b7a525..5c65bc40e 100644 --- a/src/core/meshcop/border_agent.hpp +++ b/src/core/meshcop/border_agent.hpp @@ -40,6 +40,8 @@ #include +#include "border_router/routing_manager.hpp" +#include "common/appender.hpp" #include "common/as_core_type.hpp" #include "common/heap_allocatable.hpp" #include "common/linked_list.hpp" @@ -50,6 +52,7 @@ #include "common/tasklet.hpp" #include "meshcop/dataset.hpp" #include "meshcop/secure_transport.hpp" +#include "net/dns_types.hpp" #include "net/socket.hpp" #include "net/udp6.hpp" #include "thread/tmf.hpp" @@ -69,6 +72,9 @@ namespace MeshCoP { class BorderAgent : public InstanceLocator, private NonCopyable { +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + friend class ot::BorderRouter::RoutingManager; +#endif friend class ot::Notifier; friend class Tmf::Agent; @@ -161,6 +167,23 @@ public: */ bool IsRunning(void) const { return mIsRunning; } + typedef otBorderAgentMeshCoPServiceChangedCallback MeshCoPServiceChangedCallback; + + /** + * Sets the callback function used by the Border Agent to notify any changes on the MeshCoP service TXT values. + * + * The callback is invoked when the state of MeshCoP service TXT values changes. For example, it is + * invoked when the network name or the extended PAN ID changes and pass the updated encoded TXT data to the + * application layer. + * + * This callback is invoked once right after this API is called to provide initial states of the MeshCoP + * service to the application. + * + * @param[in] aCallback The callback to invoke when there are any changes of the MeshCoP service. + * @param[in] aContext A pointer to application-specific context. + */ + void SetMeshCoPServiceChangedCallback(MeshCoPServiceChangedCallback aCallback, void *aContext); + #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE /** * Manages the ephemeral key use by Border Agent. @@ -403,6 +426,112 @@ private: uint64_t mAllocationTime; }; + class MeshCoPTxtEncoder : public InstanceLocator + { + public: + explicit MeshCoPTxtEncoder(Instance &aInstance) + : InstanceLocator(aInstance) + , mAppender(mTxtDataBuffer, sizeof(mTxtDataBuffer)) + { + } + + enum : uint8_t + { + kConnectionModeDisabled = 0, + kConnectionModePskc = 1, + kConnectionModePskd = 2, + kConnectionModeVendor = 3, + kConnectionModeX509 = 4, + }; + + enum : uint8_t + { + kThreadIfStatusNotInitialized = 0, + kThreadIfStatusInitialized = 1, + kThreadIfStatusActive = 2, + }; + + enum : uint8_t + { + kThreadRoleDisabledOrDetached = 0, + kThreadRoleChild = 1, + kThreadRoleRouter = 2, + kThreadRoleLeader = 3, + }; + + enum : uint8_t + { + kAvailabilityInfrequent = 0, + kAvailabilityHigh = 1, + }; + + struct StateBitmap + { + uint32_t mConnectionMode : 3; + uint32_t mThreadIfStatus : 2; + uint32_t mAvailability : 2; + uint32_t mBbrIsActive : 1; + uint32_t mBbrIsPrimary : 1; + uint32_t mThreadRole : 2; + uint32_t mEpskcSupported : 1; + + StateBitmap(void) + : mConnectionMode(0) + , mThreadIfStatus(0) + , mAvailability(0) + , mBbrIsActive(0) + , mBbrIsPrimary(0) + , mThreadRole(kThreadRoleDisabledOrDetached) + , mEpskcSupported(0) + { + } + + uint32_t ToUint32(void) const + { + uint32_t bitmap = 0; + + bitmap |= mConnectionMode << 0; + bitmap |= mThreadIfStatus << 3; + bitmap |= mAvailability << 5; + bitmap |= mBbrIsActive << 7; + bitmap |= mBbrIsPrimary << 8; + bitmap |= mThreadRole << 9; + bitmap |= mEpskcSupported << 11; + return bitmap; + } + }; + + Error EncodeTxtData(void); + + uint8_t *GetTxtData(void) { return mTxtDataBuffer; } + + uint16_t GetTxtDataLen(void) { return mAppender.GetAppendedLength(); } + + private: + Error AppendTxtEntry(const char *aKey, const void *aValue, uint16_t aValueLength); + + template Error AppendTxtEntry(const char *aKey, const ObjectType &aObject) + { + static_assert(!TypeTraits::IsPointer::kValue, "ObjectType must not be a pointer"); + static_assert(!TypeTraits::IsSame::kValue, "ObjectType must not be `NameData`"); + + return AppendTxtEntry(aKey, &aObject, sizeof(ObjectType)); + } + +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE + Error AppendBbrTxtEntry(StateBitmap aState); +#endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + Error AppendOmrTxtEntry(void); +#endif + + StateBitmap GetStateBitmap(void); + + static constexpr uint16_t kMaxTxtDataLen = 128; + uint8_t mTxtDataBuffer[kMaxTxtDataLen]; + Appender mAppender; + }; + void Start(void); void Stop(void); void HandleNotifierEvents(Events aEvents); @@ -421,12 +550,19 @@ private: static Coap::Message::Code CoapCodeFromError(Error aError); + void PostNotifyMeshCoPServiceChangedTask(void); + void NotifyMeshCoPServiceChanged(void); + + using NotifyMeshCoPServiceChangedTask = TaskletIn; + bool mIsRunning; Dtls::Transport mDtlsTransport; #if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE Id mId; bool mIdInitialized; #endif + Callback mMeshCoPServiceChangedCallback; + NotifyMeshCoPServiceChangedTask mNotifyMeshCoPServiceChangedTask; #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE EphemeralKeyManager mEphemeralKeyManager; #endif diff --git a/src/core/net/dns_types.hpp b/src/core/net/dns_types.hpp index cef7b3e1c..23f16bc76 100644 --- a/src/core/net/dns_types.hpp +++ b/src/core/net/dns_types.hpp @@ -1211,6 +1211,17 @@ public: */ Error AppendTo(Message &aMessage) const; + /** + * Encodes and appends the `TxtEntry` to an Appender object. + * + * @param[in] aAppender The appender to append to. + * + * @retval kErrorNone Entry was appended successfully to @p aAppender. + * @retval kErrorInvalidArgs The `TxTEntry` info is not valid. + * @retval kErrorNoBufs Insufficient available space in @p aAppender. + */ + Error AppendTo(Appender &aAppender) const; + /** * Appends an array of `TxtEntry` items to a message. * @@ -1238,7 +1249,6 @@ public: static Error AppendEntries(const TxtEntry *aEntries, uint16_t aNumEntries, MutableData &aData); private: - Error AppendTo(Appender &aAppender) const; static Error AppendEntries(const TxtEntry *aEntries, uint16_t aNumEntries, Appender &aAppender); static constexpr uint8_t kMaxKeyValueEncodedSize = 255; diff --git a/tests/nexus/test_border_agent.cpp b/tests/nexus/test_border_agent.cpp index 2081ef7f5..c251c820e 100644 --- a/tests/nexus/test_border_agent.cpp +++ b/tests/nexus/test_border_agent.cpp @@ -36,8 +36,13 @@ namespace ot { namespace Nexus { -using BorderAgent = MeshCoP::BorderAgent; -using EphemeralKeyManager = ot::MeshCoP::BorderAgent::EphemeralKeyManager; +using ActiveDatasetManager = MeshCoP::ActiveDatasetManager; +using BorderAgent = MeshCoP::BorderAgent; +using EphemeralKeyManager = ot::MeshCoP::BorderAgent::EphemeralKeyManager; +using ExtendedPanIdManager = MeshCoP::ExtendedPanIdManager; +using NameData = MeshCoP::NameData; +using NetworkNameManager = MeshCoP::NetworkNameManager; +using TxtEntry = Dns::TxtEntry; void TestBorderAgent(void) { @@ -737,6 +742,160 @@ void TestBorderAgentEphemeralKey(void) VerifyOrQuit(node0.Get().GetCounters().mEpskcInvalidArgsErrors == 2); } +class MeshCoPServiceTester +{ +public: + MeshCoPServiceTester(BorderAgent &aBorderAgent) + : mBorderAgent(aBorderAgent) + , mIsRunning(false) + , mUdpPort(0) + { + } + + void HandleMeshCoPServiceChanged(const uint8_t *aTxtData, uint16_t aLength) + { + mIsRunning = mBorderAgent.IsRunning(); + mUdpPort = mBorderAgent.GetUdpPort(); + + assert(aLength <= kMaxTxtDataLen); + + memcpy(mTxtData, aTxtData, aLength); + mTxtDataLength = aLength; + } + + bool FindTxtEntry(const char *aKey, TxtEntry &aTxtEntry) + { + bool found = false; + TxtEntry::Iterator iter; + + iter.Init(mTxtData, mTxtDataLength); + while (iter.GetNextEntry(aTxtEntry) == kErrorNone) + { + if (strcmp(aTxtEntry.mKey, aKey) == 0) + { + found = true; + break; + } + } + + return found; + } + + static constexpr uint16_t kMaxTxtDataLen = 128; + + BorderAgent &mBorderAgent; + uint8_t mTxtData[kMaxTxtDataLen]; + uint16_t mTxtDataLength; + bool mIsRunning; + uint16_t mUdpPort; +}; + +static void HandleMeshCoPServiceChanged(const uint8_t *aTxtData, uint16_t aLength, void *aContext) +{ + static_cast(aContext)->HandleMeshCoPServiceChanged(aTxtData, aLength); +} + +template bool CheckObjectSameAsTxtEntryData(const TxtEntry &aTxtEntry, const ObjectType &aObject) +{ + static_assert(!TypeTraits::IsPointer::kValue, "ObjectType must not be a pointer"); + + return aTxtEntry.mValueLength == sizeof(ObjectType) && memcmp(aTxtEntry.mValue, &aObject, sizeof(ObjectType)) == 0; +} + +template <> bool CheckObjectSameAsTxtEntryData(const TxtEntry &aTxtEntry, const NameData &aNameData) +{ + return aTxtEntry.mValueLength == aNameData.GetLength() && + memcmp(aTxtEntry.mValue, aNameData.GetBuffer(), aNameData.GetLength()) == 0; +} + +#if OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_1 +static const char kThreadVersionString[] = "1.1.1"; +#elif OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_2 +static const char kThreadVersionString[] = "1.2.0"; +#elif OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_3 +static const char kThreadVersionString[] = "1.3.0"; +#elif OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_3_1 +static const char kThreadVersionString[] = "1.3.1"; +#elif OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_4 +static const char kThreadVersionString[] = "1.4.0"; +#endif + +void TestBorderAgentMeshCoPServiceChangedCallback(void) +{ + Core nexus; + Node &node0 = nexus.CreateNode(); + + Log("------------------------------------------------------------------------------------------------------"); + Log("TestBorderAgentMeshCoPServiceChangedCallback"); + + nexus.AdvanceTime(0); + MeshCoPServiceTester meshCoPServiceTester(node0.Get()); + TxtEntry txtEntry; + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // 1. Set MeshCoP service change callback. Will get initial values. + Log("Set MeshCoP service change callback and check initial values"); + node0.Get().SetMeshCoPServiceChangedCallback(HandleMeshCoPServiceChanged, &meshCoPServiceTester); + nexus.AdvanceTime(1); + + // 1.1 Check the initial TXT entries +#if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("id", txtEntry)); + BorderAgent::Id id; + VerifyOrQuit(node0.Get().GetId(id) == kErrorNone); + VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, id)); +#endif + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("nn", txtEntry)); + VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, node0.Get().GetNetworkName().GetAsData())); + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("xp", txtEntry)); + VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, node0.Get().GetExtPanId())); + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("tv", txtEntry)); + VerifyOrQuit( + CheckObjectSameAsTxtEntryData(txtEntry, NameData(kThreadVersionString, sizeof(kThreadVersionString) - 1))); + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("xa", txtEntry)); + VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, node0.Get().GetExtAddress())); + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("sb", txtEntry)); + + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("pt", txtEntry) == false); + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("at", txtEntry) == false); + + // 1.2 Check the Border Agent state + VerifyOrQuit(meshCoPServiceTester.mIsRunning == false); + VerifyOrQuit(meshCoPServiceTester.mUdpPort == 0); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // 2. Join Thread network and check updated values and states. + Log("Join Thread network and check updated Txt data and states"); + node0.Form(); + nexus.AdvanceTime(50 * Time::kOneSecondInMsec); + + // 2.1 Check the initial TXT entries +#if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("id", txtEntry)); + VerifyOrQuit(node0.Get().GetId(id) == kErrorNone); + VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, id)); +#endif + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("nn", txtEntry)); + VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, node0.Get().GetNetworkName().GetAsData())); + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("xp", txtEntry)); + VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, node0.Get().GetExtPanId())); + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("tv", txtEntry)); + VerifyOrQuit( + CheckObjectSameAsTxtEntryData(txtEntry, NameData(kThreadVersionString, sizeof(kThreadVersionString) - 1))); + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("xa", txtEntry)); + VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, node0.Get().GetExtAddress())); + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("sb", txtEntry)); + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("pt", txtEntry)); + VerifyOrQuit(CheckObjectSameAsTxtEntryData( + txtEntry, BigEndian::HostSwap32(node0.Get().GetLeaderData().GetPartitionId()))); + VerifyOrQuit(meshCoPServiceTester.FindTxtEntry("at", txtEntry)); + VerifyOrQuit(CheckObjectSameAsTxtEntryData(txtEntry, node0.Get().GetTimestamp())); + + // 2.2 Check the Border Agent state + VerifyOrQuit(meshCoPServiceTester.mIsRunning == true); + VerifyOrQuit(meshCoPServiceTester.mUdpPort != 0); +} + } // namespace Nexus } // namespace ot @@ -744,6 +903,7 @@ int main(void) { ot::Nexus::TestBorderAgent(); ot::Nexus::TestBorderAgentEphemeralKey(); + ot::Nexus::TestBorderAgentMeshCoPServiceChangedCallback(); printf("All tests passed\n"); return 0; }