From 9681690fab100590566e4937cbf2d072de031ff3 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Mon, 22 Apr 2024 10:54:47 -0700 Subject: [PATCH] [mdns] add APIs to iterate over registered host/service/key entries (#9954) --- include/openthread/instance.h | 2 +- include/openthread/mdns.h | 122 +++++++++++++++++-- src/cli/cli_mdns.cpp | 141 +++++++++++++++++++++- src/cli/cli_mdns.hpp | 1 + src/core/api/mdns_api.cpp | 42 +++++++ src/core/net/mdns.cpp | 220 +++++++++++++++++++++++++++++++++- src/core/net/mdns.hpp | 155 +++++++++++++++++++++--- 7 files changed, 647 insertions(+), 36 deletions(-) diff --git a/include/openthread/instance.h b/include/openthread/instance.h index ac135e860..df8bf4186 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 (406) +#define OPENTHREAD_API_VERSION (407) /** * @addtogroup api-instance diff --git a/include/openthread/mdns.h b/include/openthread/mdns.h index a0a3045d9..a88c70f83 100644 --- a/include/openthread/mdns.h +++ b/include/openthread/mdns.h @@ -133,6 +133,24 @@ typedef otPlatDnssdService otMdnsService; */ typedef otPlatDnssdKey otMdnsKey; +/** + * Represents an mDNS entry iterator. + * + */ +typedef struct otMdnsIterator otMdnsIterator; + +/** + * Represents a host/service/key entry state. + * + */ +typedef enum otMdnsEntryState +{ + OT_MDNS_ENTRY_STATE_PROBING, ///< Probing to claim the name. + OT_MDNS_ENTRY_STATE_REGISTERED, ///< Entry is successfully registered. + OT_MDNS_ENTRY_STATE_CONFLICT, ///< Name conflict was detected. + OT_MDNS_ENTRY_STATE_REMOVING, ///< Entry is being removed (sending "goodbye" announcements). +} otMdnsEntryState; + /** * Enables or disables the mDNS module. * @@ -335,10 +353,11 @@ otError otMdnsUnregisterService(otInstance *aInstance, const otMdnsService *aSer * * The fields in @p aKey follow these rules: * - * - If the key is associated with a host entry, `mName` specifies the host name and `mServcieType` MUST be NULL. - * - If the key is associated with a service entry, `mName` specifies the service instance label (always treated as - * a single label) and `mServiceType` specifies the service type (e.g., "_tst._udp"). In this case the DNS name for - * key record is `.`. + * - If the key is associated with a host entry, the `mName` field specifies the host name and the `mServiceType` MUST + * be NULL. + * - If the key is associated with a service entry, the `mName` filed specifies the service instance label (always + * treated as a single label) and the `mServiceType` filed specifies the service type (e.g., "_tst._udp"). In this + * case the DNS name for key record is `.`. * - The `mKeyData` field contains the key record's data with `mKeyDataLength` as its length in byes. * - The `mTtl` specifies the TTL if non-zero. If zero, the mDNS module will use the default TTL of 120 seconds. * - Other fields in @p aKey structure are ignored in an `otMdnsRegisterKey()` call. @@ -368,10 +387,11 @@ otError otMdnsRegisterKey(otInstance *aInstance, * * The fields in @p aKey follow these rules: * - * - If the key is associated with a host entry, `mName` specifies the host name and `mServcieType` MUST be NULL. - * - If the key is associated with a service entry, `mName` specifies the service instance label (always treated as - * a single label) and `mServiceType` specifies the service type (e.g., "_tst._udp"). In this case the DNS name for - * key record is `.`. + * - If the key is associated with a host entry, the `mName` field specifies the host name and the `mServiceType` MUST + * be NULL. + * - If the key is associated with a service entry, the `mName` filed specifies the service instance label (always + * treated as a single label) and the `mServiceType` filed specifies the service type (e.g., "_tst._udp"). In this + * case the DNS name for key record is `.`. * - Other fields in @p aKey structure are ignored in an `otMdnsUnregisterKey()` call. * * If there is no previously registered key with the same name, no action is performed. @@ -388,6 +408,88 @@ otError otMdnsRegisterKey(otInstance *aInstance, */ otError otMdnsUnregisterKey(otInstance *aInstance, const otMdnsKey *aKey); +/** + * Allocates a new iterator. + * + * An allocated iterator must be freed by the caller using `otMdnsFreeIterator()`. + * + * @param[in] aInstance The OpenThread instance. + * + * @returns A pointer to the allocated iterator, or `NULL` if it fails to allocate. + * + */ +otMdnsIterator *otMdnsAllocateIterator(otInstance *aInstance); + +/** + * Frees a previously allocated iterator. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aIterator The iterator to free. + * + */ +void otMdnsFreeIterator(otInstance *aInstance, otMdnsIterator *aIterator); + +/** + * Iterates over registered host entries. + * + * On success, @p aHost is populated with information about the next host. Pointers within the `otMdnsHost` structure + * (like `mName`) remain valid until the next call to any OpenThread stack's public or platform API/callback. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aIterator Pointer to the iterator. + * @param[out] aHost Pointer to an `otMdnsHost` to return the information about the next host entry. + * @param[out] aState Pointer to an `otMdnsEntryState` to return the entry state. + * + * @retval OT_ERROR_NONE @p aHost, @p aState, & @p aIterator are updated successfully. + * @retval OT_ERROR_NOT_FOUND Reached the end of the list. + * @retval OT_ERROR_INVALID_ARG @p aIterator is not valid. + * + */ +otError otMdnsGetNextHost(otInstance *aInstance, + otMdnsIterator *aIterator, + otMdnsHost *aHost, + otMdnsEntryState *aState); + +/** + * Iterates over registered service entries. + * + * On success, @p aService is populated with information about the next service . Pointers within the `otMdnsService` + * structure (like `mServiceType`, `mSubTypeLabels`) remain valid until the next call to any OpenThread stack's public + * or platform API/callback. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aIterator Pointer to the iterator to use. + * @param[out] aService Pointer to an `otMdnsService` to return the information about the next service entry. + * @param[out] aState Pointer to an `otMdnsEntryState` to return the entry state. + * + * @retval OT_ERROR_NONE @p aService, @p aState, & @p aIterator are updated successfully. + * @retval OT_ERROR_NOT_FOUND Reached the end of the list. + * @retval OT_ERROR_INVALID_ARG @p aIterator is not valid. + * + */ +otError otMdnsGetNextService(otInstance *aInstance, + otMdnsIterator *aIterator, + otMdnsService *aService, + otMdnsEntryState *aState); + +/** + * Iterates over registered key entries. + * + * On success, @p aKey is populated with information about the next key. Pointers within the `otMdnsKey` structure + * (like `mName`) remain valid until the next call to any OpenThread stack's public or platform API/callback. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aIterator Pointer to the iterator to use. + * @param[out] aKey Pointer to an `otMdnsKey` to return the information about the next key entry. + * @param[out] aState Pointer to an `otMdnsEntryState` to return the entry state. + * + * @retval OT_ERROR_NONE @p aKey, @p aState, & @p aIterator are updated successfully. + * @retval OT_ERROR_NOT_FOUND Reached the end of the list. + * @retval OT_ERROR_INVALID_ARG Iterator is not valid. + * + */ +otError otMdnsGetNextKey(otInstance *aInstance, otMdnsIterator *aIterator, otMdnsKey *aKey, otMdnsEntryState *aState); + typedef struct otMdnsBrowseResult otMdnsBrowseResult; typedef struct otMdnsSrvResult otMdnsSrvResult; typedef struct otMdnsTxtResult otMdnsTxtResult; @@ -661,7 +763,7 @@ otError otMdnsStopTxtResolver(otInstance *aInstance, const otMdnsTxtResolver *aR * * Initiates a continuous IPv6 address resolver for the specified host name in @p aResolver. * - * Discovered addresses are reported through the `mCallback` function in @ p aResolver. The callback is invoked + * Discovered addresses are reported through the `mCallback` function in @p aResolver. The callback is invoked * whenever addresses are added or removed, providing an updated list. If all addresses are removed, the callback is * invoked with an empty list (`mAddresses` will be NULL, and `mAddressesLength` will be zero). * @@ -700,7 +802,7 @@ otError otMdnsStopIp6AddressResolver(otInstance *aInstance, const otMdnsAddressR * * Initiates a continuous IPv4 address resolver for the specified host name in @p aResolver. * - * Discovered addresses are reported through the `mCallback` function in @ p aResolver. The IPv4 addresses are + * Discovered addresses are reported through the `mCallback` function in @p aResolver. The IPv4 addresses are * represented using the IPv4-mapped IPv6 address format in `mAddresses` array. The callback is invoked whenever * addresses are added or removed, providing an updated list. If all addresses are removed, the callback is invoked * with an empty list (`mAddresses` will be NULL, and `mAddressesLength` will be zero). diff --git a/src/cli/cli_mdns.cpp b/src/cli/cli_mdns.cpp index 44ae0c838..977bb7fa1 100644 --- a/src/cli/cli_mdns.cpp +++ b/src/cli/cli_mdns.cpp @@ -148,6 +148,29 @@ void Mdns::OutputKey(const otMdnsKey &aKey) OutputLine(kIndentSize, "ttl: %lu", ToUlong(aKey.mTtl)); } +void Mdns::OutputState(otMdnsEntryState aState) +{ + const char *stateString = ""; + + switch (aState) + { + case OT_MDNS_ENTRY_STATE_PROBING: + stateString = "probing"; + break; + case OT_MDNS_ENTRY_STATE_REGISTERED: + stateString = "registered"; + break; + case OT_MDNS_ENTRY_STATE_CONFLICT: + stateString = "conflict"; + break; + case OT_MDNS_ENTRY_STATE_REMOVING: + stateString = "removing"; + break; + } + + OutputLine(kIndentSize, "state: %s", stateString); +} + template <> otError Mdns::Process(Arg aArgs[]) { // mdns [async] [host|service|key] @@ -458,6 +481,117 @@ exit: return error; } +template <> otError Mdns::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + otMdnsIterator *iterator = nullptr; + otMdnsHost host; + otMdnsEntryState state; + + VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + iterator = otMdnsAllocateIterator(GetInstancePtr()); + VerifyOrExit(iterator != nullptr, error = OT_ERROR_NO_BUFS); + + while (true) + { + error = otMdnsGetNextHost(GetInstancePtr(), iterator, &host, &state); + + if (error == OT_ERROR_NOT_FOUND) + { + error = OT_ERROR_NONE; + ExitNow(); + } + + SuccessOrExit(error); + + OutputHost(host); + OutputState(state); + } + +exit: + if (iterator != nullptr) + { + otMdnsFreeIterator(GetInstancePtr(), iterator); + } + + return error; +} + +template <> otError Mdns::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + otMdnsIterator *iterator = nullptr; + otMdnsService service; + otMdnsEntryState state; + + VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + iterator = otMdnsAllocateIterator(GetInstancePtr()); + VerifyOrExit(iterator != nullptr, error = OT_ERROR_NO_BUFS); + + while (true) + { + error = otMdnsGetNextService(GetInstancePtr(), iterator, &service, &state); + + if (error == OT_ERROR_NOT_FOUND) + { + error = OT_ERROR_NONE; + ExitNow(); + } + + SuccessOrExit(error); + + OutputService(service); + OutputState(state); + } + +exit: + if (iterator != nullptr) + { + otMdnsFreeIterator(GetInstancePtr(), iterator); + } + + return error; +} + +template <> otError Mdns::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + otMdnsIterator *iterator = nullptr; + otMdnsKey key; + otMdnsEntryState state; + + VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + iterator = otMdnsAllocateIterator(GetInstancePtr()); + VerifyOrExit(iterator != nullptr, error = OT_ERROR_NO_BUFS); + + while (true) + { + error = otMdnsGetNextKey(GetInstancePtr(), iterator, &key, &state); + + if (error == OT_ERROR_NOT_FOUND) + { + error = OT_ERROR_NONE; + ExitNow(); + } + + SuccessOrExit(error); + + OutputKey(key); + OutputState(state); + } + +exit: + if (iterator != nullptr) + { + otMdnsFreeIterator(GetInstancePtr(), iterator); + } + + return error; +} + otError Mdns::ParseStartOrStop(const Arg &aArg, bool &aIsStart) { otError error = OT_ERROR_NONE; @@ -743,9 +877,10 @@ otError Mdns::Process(Arg aArgs[]) } static constexpr Command kCommands[] = { - CmdEntry("browser"), CmdEntry("disable"), CmdEntry("enable"), CmdEntry("ip4resolver"), - CmdEntry("ip6resolver"), CmdEntry("register"), CmdEntry("srvresolver"), CmdEntry("state"), - CmdEntry("txtresolver"), CmdEntry("unicastquestion"), CmdEntry("unregister"), + CmdEntry("browser"), CmdEntry("disable"), CmdEntry("enable"), CmdEntry("hosts"), + CmdEntry("ip4resolver"), CmdEntry("ip6resolver"), CmdEntry("keys"), CmdEntry("register"), + CmdEntry("services"), CmdEntry("srvresolver"), CmdEntry("state"), CmdEntry("txtresolver"), + CmdEntry("unicastquestion"), CmdEntry("unregister"), }; #undef CmdEntry diff --git a/src/cli/cli_mdns.hpp b/src/cli/cli_mdns.hpp index 0c189dc8b..242b6d9a4 100644 --- a/src/cli/cli_mdns.hpp +++ b/src/cli/cli_mdns.hpp @@ -110,6 +110,7 @@ private: void OutputHost(const otMdnsHost &aHost); void OutputService(const otMdnsService &aService); void OutputKey(const otMdnsKey &aKey); + void OutputState(otMdnsEntryState aState); otError ProcessRegisterHost(Arg aArgs[]); otError ProcessRegisterService(Arg aArgs[]); otError ProcessRegisterKey(Arg aArgs[]); diff --git a/src/core/api/mdns_api.cpp b/src/core/api/mdns_api.cpp index 797153947..8563303e6 100644 --- a/src/core/api/mdns_api.cpp +++ b/src/core/api/mdns_api.cpp @@ -115,6 +115,48 @@ otError otMdnsUnregisterKey(otInstance *aInstance, const otMdnsKey *aKey) return AsCoreType(aInstance).Get().UnregisterKey(*aKey); } +otMdnsIterator *otMdnsAllocateIterator(otInstance *aInstance) +{ + return AsCoreType(aInstance).Get().AllocateIterator(); +} + +void otMdnsFreeIterator(otInstance *aInstance, otMdnsIterator *aIterator) +{ + AssertPointerIsNotNull(aIterator); + + AsCoreType(aInstance).Get().FreeIterator(*aIterator); +} + +otError otMdnsGetNextHost(otInstance *aInstance, otMdnsIterator *aIterator, otMdnsHost *aHost, otMdnsEntryState *aState) +{ + AssertPointerIsNotNull(aIterator); + AssertPointerIsNotNull(aHost); + AssertPointerIsNotNull(aState); + + return AsCoreType(aInstance).Get().GetNextHost(*aIterator, *aHost, *aState); +} + +otError otMdnsGetNextService(otInstance *aInstance, + otMdnsIterator *aIterator, + otMdnsService *aService, + otMdnsEntryState *aState) +{ + AssertPointerIsNotNull(aIterator); + AssertPointerIsNotNull(aService); + AssertPointerIsNotNull(aState); + + return AsCoreType(aInstance).Get().GetNextService(*aIterator, *aService, *aState); +} + +otError otMdnsGetNextKey(otInstance *aInstance, otMdnsIterator *aIterator, otMdnsKey *aKey, otMdnsEntryState *aState) +{ + AssertPointerIsNotNull(aIterator); + AssertPointerIsNotNull(aKey); + AssertPointerIsNotNull(aState); + + return AsCoreType(aInstance).Get().GetNextKey(*aIterator, *aKey, *aState); +} + otError otMdnsStartBrowser(otInstance *aInstance, const otMdnsBrowser *aBroswer) { AssertPointerIsNotNull(aBroswer); diff --git a/src/core/net/mdns.cpp b/src/core/net/mdns.cpp index e85e226c3..ddf699539 100644 --- a/src/core/net/mdns.cpp +++ b/src/core/net/mdns.cpp @@ -201,6 +201,25 @@ Error Core::UnregisterKey(const Key &aKey) return IsKeyForService(aKey) ? Unregister(aKey) : Unregister(aKey); } +Core::Iterator *Core::AllocateIterator(void) { return EntryIterator::Allocate(GetInstance()); } + +void Core::FreeIterator(Iterator &aIterator) { static_cast(aIterator).Free(); } + +Error Core::GetNextHost(Iterator &aIterator, Host &aHost, EntryState &aState) const +{ + return static_cast(aIterator).GetNextHost(aHost, aState); +} + +Error Core::GetNextService(Iterator &aIterator, Service &aService, EntryState &aState) const +{ + return static_cast(aIterator).GetNextService(aService, aState); +} + +Error Core::GetNextKey(Iterator &aIterator, Key &aKey, EntryState &aState) const +{ + return static_cast(aIterator).GetNextKey(aKey, aState); +} + void Core::InvokeConflictCallback(const char *aName, const char *aServiceType) { if (mConflictCallback != nullptr) @@ -1256,6 +1275,23 @@ void Core::Entry::AppendNsecRecordTo(TxMessage &aTxMessage, mAppendedNsec = true; } +Error Core::Entry::CopyKeyInfoTo(Key &aKey, EntryState &aState) const +{ + Error error = kErrorNone; + + VerifyOrExit(mKeyRecord.IsPresent(), error = kErrorNotFound); + + aKey.mKeyData = mKeyData.GetBytes(); + aKey.mKeyDataLength = mKeyData.GetLength(); + aKey.mClass = ResourceRecord::kClassInternet; + aKey.mTtl = mKeyRecord.GetTtl(); + aKey.mInfraIfIndex = Get().mInfraIfIndex; + aState = static_cast(GetState()); + +exit: + return error; +} + //---------------------------------------------------------------------------------------------------------------------- // Core::HostEntry @@ -1584,6 +1620,36 @@ exit: return; } +Error Core::HostEntry::CopyInfoTo(Host &aHost, EntryState &aState) const +{ + Error error = kErrorNone; + + VerifyOrExit(mAddrRecord.IsPresent(), error = kErrorNotFound); + + aHost.mHostName = mName.AsCString(); + aHost.mAddresses = mAddresses.AsCArray(); + aHost.mAddressesLength = mAddresses.GetLength(); + aHost.mTtl = mAddrRecord.GetTtl(); + aHost.mInfraIfIndex = Get().mInfraIfIndex; + aState = static_cast(GetState()); + +exit: + return error; +} + +Error Core::HostEntry::CopyInfoTo(Key &aKey, EntryState &aState) const +{ + Error error; + + SuccessOrExit(error = CopyKeyInfoTo(aKey, aState)); + + aKey.mName = mName.AsCString(); + aKey.mServiceType = nullptr; + +exit: + return error; +} + //---------------------------------------------------------------------------------------------------------------------- // Core::ServiceEntry @@ -2198,9 +2264,10 @@ void Core::ServiceEntry::UpdateServiceTypes(void) // This method updates the `mServiceTypes` list adding or // removing this `ServiceEntry` info. // - // It is called whenever `ServcieEntry` state gets changed or an - // PTR record is added or removed. The service is valid when - // entry is registered and we have a PTR with non-zero TTL. + // It is called whenever the `ServiceEntry` state gets changed + // or a PTR record is added or removed. The service is valid + // when entry is registered and we have a PTR with non-zero + // TTL. bool shouldAdd = (GetState() == kRegistered) && mPtrRecord.CanAnswer(); ServiceType *serviceType; @@ -2235,7 +2302,7 @@ void Core::ServiceEntry::UpdateServiceTypes(void) // the `mServiceTypes` list. It is safe to // remove here as this method will never be // called while we are iterating over the - // `mServcieTypes` list. + // `mServiceTypes` list. Get().mServiceTypes.RemoveMatching(*serviceType); } @@ -2430,6 +2497,50 @@ exit: return; } +Error Core::ServiceEntry::CopyInfoTo(Service &aService, EntryState &aState, EntryIterator &aIterator) const +{ + Error error = kErrorNone; + + VerifyOrExit(mPtrRecord.IsPresent(), error = kErrorNotFound); + + aIterator.mSubTypeArray.Free(); + + for (const SubType &subType : mSubTypes) + { + SuccessOrAssert(aIterator.mSubTypeArray.PushBack(subType.mLabel.AsCString())); + } + + aService.mHostName = mHostName.AsCString(); + aService.mServiceInstance = mServiceInstance.AsCString(); + aService.mServiceType = mServiceType.AsCString(); + aService.mSubTypeLabels = aIterator.mSubTypeArray.AsCArray(); + aService.mSubTypeLabelsLength = aIterator.mSubTypeArray.GetLength(); + aService.mTxtData = mTxtData.GetBytes(); + aService.mTxtDataLength = mTxtData.GetLength(); + aService.mPort = mPort; + aService.mPriority = mPriority; + aService.mWeight = mWeight; + aService.mTtl = mPtrRecord.GetTtl(); + aService.mInfraIfIndex = Get().mInfraIfIndex; + aState = static_cast(GetState()); + +exit: + return error; +} + +Error Core::ServiceEntry::CopyInfoTo(Key &aKey, EntryState &aState) const +{ + Error error; + + SuccessOrExit(error = CopyKeyInfoTo(aKey, aState)); + + aKey.mName = mServiceInstance.AsCString(); + aKey.mServiceType = mServiceType.AsCString(); + +exit: + return error; +} + //---------------------------------------------------------------------------------------------------------------------- // Core::ServiceEntry::SubType @@ -2486,9 +2597,9 @@ exit: return error; } -bool Core::ServiceType::Matches(const Name &aServcieTypeName) const +bool Core::ServiceType::Matches(const Name &aServiceTypeName) const { - return aServcieTypeName.Matches(/* aFirstLabel */ nullptr, mServiceType.AsCString(), kLocalDomain); + return aServiceTypeName.Matches(/* aFirstLabel */ nullptr, mServiceType.AsCString(), kLocalDomain); } bool Core::ServiceType::Matches(const Heap::String &aServiceType) const @@ -5984,6 +6095,103 @@ exit: void Core::Ip4AddrCache::PrepareAQuestion(TxMessage &aQuery) { PrepareQueryQuestion(aQuery, ResourceRecord::kTypeA); } +//--------------------------------------------------------------------------------------------------------------------- +// Core::Iterator + +Core::EntryIterator::EntryIterator(Instance &aInstance) + : InstanceLocator(aInstance) + , mType(kUnspecified) +{ +} + +Error Core::EntryIterator::GetNextHost(Host &aHost, EntryState &aState) +{ + Error error = kErrorNotFound; + + if (mType == kUnspecified) + { + mHostEntry = Get().mHostEntries.GetHead(); + mType = kHost; + } + else + { + VerifyOrExit(mType == kHost, error = kErrorInvalidArgs); + } + + while (error == kErrorNotFound) + { + VerifyOrExit(mHostEntry != nullptr); + error = mHostEntry->CopyInfoTo(aHost, aState); + mHostEntry = mHostEntry->GetNext(); + } + +exit: + return error; +} + +Error Core::EntryIterator::GetNextService(Service &aService, EntryState &aState) +{ + Error error = kErrorNotFound; + + if (mType == kUnspecified) + { + mServiceEntry = Get().mServiceEntries.GetHead(); + mType = kService; + } + else + { + VerifyOrExit(mType == kService, error = kErrorInvalidArgs); + } + + while (error == kErrorNotFound) + { + VerifyOrExit(mServiceEntry != nullptr); + error = mServiceEntry->CopyInfoTo(aService, aState, *this); + mServiceEntry = mServiceEntry->GetNext(); + } + +exit: + return error; +} + +Error Core::EntryIterator::GetNextKey(Key &aKey, EntryState &aState) +{ + Error error = kErrorNotFound; + + if (mType == kUnspecified) + { + mHostEntry = Get().mHostEntries.GetHead(); + mType = kHostKey; + } + else + { + VerifyOrExit((mType == kServiceKey) || (mType == kHostKey), error = kErrorInvalidArgs); + } + + while ((error == kErrorNotFound) && (mType == kHostKey)) + { + if (mHostEntry == nullptr) + { + mServiceEntry = Get().mServiceEntries.GetHead(); + mType = kServiceKey; + break; + } + + error = mHostEntry->CopyInfoTo(aKey, aState); + mHostEntry = mHostEntry->GetNext(); + } + + while ((error == kErrorNotFound) && (mType == kServiceKey)) + { + VerifyOrExit(mServiceEntry != nullptr); + error = mServiceEntry->CopyInfoTo(aKey, aState); + mServiceEntry = mServiceEntry->GetNext(); + } + +exit: + return error; +} + } // namespace Multicast } // namespace Dns } // namespace ot diff --git a/src/core/net/mdns.hpp b/src/core/net/mdns.hpp index 03fbf041e..5ee323657 100644 --- a/src/core/net/mdns.hpp +++ b/src/core/net/mdns.hpp @@ -46,6 +46,7 @@ #include "common/heap_data.hpp" #include "common/heap_string.hpp" #include "common/linked_list.hpp" +#include "common/locator.hpp" #include "common/owned_ptr.hpp" #include "common/owning_list.hpp" #include "common/retain_ptr.hpp" @@ -63,6 +64,14 @@ * */ +/** + * Represents an opaque (and empty) type for an mDNS iterator. + * + */ +struct otMdnsIterator +{ +}; + namespace ot { namespace Dns { namespace Multicast { @@ -95,6 +104,7 @@ public: typedef otMdnsRequestId RequestId; ///< A request Identifier. typedef otMdnsRegisterCallback RegisterCallback; ///< Registration callback. typedef otMdnsConflictCallback ConflictCallback; ///< Conflict callback. + typedef otMdnsEntryState EntryState; ///< Host/Service/Key entry state. typedef otMdnsHost Host; ///< Host information. typedef otMdnsService Service; ///< Service information. typedef otMdnsKey Key; ///< Key information. @@ -111,6 +121,7 @@ public: typedef otMdnsAddressCallback AddressCallback; ///< Address callback typedef otMdnsAddressResult AddressResult; ///< Address result. typedef otMdnsAddressAndTtl AddressAndTtl; ///< Address and TTL. + typedef otMdnsIterator Iterator; ///< An entry iterator. /** * Represents a socket address info. @@ -323,10 +334,11 @@ public: * * The fields in @p aKey follow these rules: * - * - If the key is associated with a host entry, `mName` specifies the host name & `mServcieType` MUST be `nullptr`. - * - If the key is associated with a service entry, `mName` specifies the service instance label (always treated as - * a single label) and `mServiceType` specifies the service type (e.g. "_tst._udp"). In this case the DNS name - * for key record is `.`. + * - If the key is associated with a host entry, the `mName` field specifies the host name and the `mServiceType` + * MUST be `nullptr`. + * - If the key is associated with a service entry, the `mName` filed specifies the service instance label (always + * treated as a single label) and the `mServiceType` filed specifies the service type (e.g. "_tst._udp"). In this + * case the DNS name for key record is `.`. * - The `mKeyData` field contains the key record's data with `mKeyDataLength` as its length in byes. * - The `mTtl` specifies the TTL if non-zero. If zero, the mDNS module will use default TTL for the key entry. * @@ -351,10 +363,11 @@ public: * * The fields in @p aKey follow these rules: * - * - If the key is associated with a host entry, `mName` specifies the host name & `mServcieType` MUST be `nullptr`. - * - If the key is associated with a service entry, `mName` specifies the service instance label (always treated as - * a single label) and `mServiceType` specifies the service type (e.g. "_tst._udp"). In this case the DNS name - * for key record is `.`. + * - If the key is associated with a host entry, the `mName` field specifies the host name and the `mServiceType` + * MUST be `nullptr`. + * - If the key is associated with a service entry, the `mName` filed specifies the service instance label (always + * treated as a single label) and the `mServiceType` field specifies the service type (e.g. "_tst._udp"). In this + * case the DNS name for key record is `.`. * - The rest of the fields in @p aKey structure are ignored in a`otMdnsUnregisterKey()` call. * * If there is no previously registered key with the same name, no action is performed. @@ -484,7 +497,7 @@ public: * * Initiates a continuous IPv6 address resolver for the specified host name in @p aResolver. * - * Discovered addresses are reported through the `mCallback` function in @ p aResolver. The callback is invoked + * Discovered addresses are reported through the `mCallback` function in @p aResolver. The callback is invoked * whenever addresses are added or removed, providing an updated list. If all addresses are removed, the callback * is invoked with an empty list (`mAddresses` will be NULL, and `mAddressesLength` will be zero). * @@ -521,7 +534,7 @@ public: * * Initiates a continuous IPv4 address resolver for the specified host name in @p aResolver. * - * Discovered addresses are reported through the `mCallback` function in @ p aResolver. The IPv4 addresses are + * Discovered addresses are reported through the `mCallback` function in @p aResolver. The IPv4 addresses are * represented using the IPv4-mapped IPv6 address format in `mAddresses` array. The callback is invoked whenever * addresses are added or removed, providing an updated list. If all addresses are removed, the callback is invoked * with an empty list (`mAddresses` will be NULL, and `mAddressesLength` will be zero). @@ -564,6 +577,72 @@ public: */ void SetMaxMessageSize(uint16_t aMaxSize) { mMaxMessageSize = aMaxSize; } + /** + * Allocates a new iterator. + * + * @returns A pointer to the newly allocated iterator or `nullptr` if it fails to allocate. + * + */ + Iterator *AllocateIterator(void); + + /** + * Frees a previously allocated iterator. + * + * @param[in] aIterator The iterator to free. + * + */ + void FreeIterator(Iterator &aIterator); + + /** + * Iterates over registered host entries. + * + * On success, @p aHost is populated with information about the next host. Pointers within the `Host` structure + * (like `mName`) remain valid until the next call to any OpenThread stack's public or platform API/callback. + * + * @param[in] aIterator The iterator to use. + * @param[out] aHost A `Host` to return the information about the next host entry. + * @param[out] aState An `EntryState` to return the entry state. + * + * @retval kErrorNone @p aHost, @p aState, & @p aIterator are updated successfully. + * @retval kErrorNotFound Reached the end of the list. + * @retval kErrorInvalidArg @p aIterator is not valid. + * + */ + Error GetNextHost(Iterator &aIterator, Host &aHost, EntryState &aState) const; + + /** + * Iterates over registered service entries. + * + * On success, @p aService is populated with information about the next service. Pointers within the `Service` + * structure (like `mServiceType`) remain valid until the next call to any OpenThread stack's public or platform + * API/callback. + * + * @param[out] aService A `Service` to return the information about the next service entry. + * @param[out] aState An `EntryState` to return the entry state. + * + * @retval kErrorNone @p aService, @p aState, & @p aIterator are updated successfully. + * @retval kErrorNotFound Reached the end of the list. + * @retval kErrorInvalidArg @p aIterator is not valid. + * + */ + Error GetNextService(Iterator &aIterator, Service &aService, EntryState &aState) const; + + /** + * Iterates over registered key entries. + * + * On success, @p aKey is populated with information about the next key. Pointers within the `Key` structure + * (like `mName`) remain valid until the next call to any OpenThread stack's public or platform API/callback. + * + * @param[out] aKey A `Key` to return the information about the next key entry. + * @param[out] aState An `EntryState` to return the entry state. + * + * @retval kErrorNone @p aKey, @p aState, & @p aIterator are updated successfully. + * @retval kErrorNotFound Reached the end of the list. + * @retval kErrorInvalidArg @p aIterator is not valid. + * + */ + Error GetNextKey(Iterator &aIterator, Key &aKey, EntryState &aState) const; + private: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -626,6 +705,7 @@ private: class RxMessage; class ServiceEntry; class ServiceType; + class EntryIterator; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -792,17 +872,19 @@ private: public: enum State : uint8_t { - kProbing, - kRegistered, - kConflict, - kRemoving, + kProbing = OT_MDNS_ENTRY_STATE_PROBING, + kRegistered = OT_MDNS_ENTRY_STATE_REGISTERED, + kConflict = OT_MDNS_ENTRY_STATE_CONFLICT, + kRemoving = OT_MDNS_ENTRY_STATE_REMOVING, }; State GetState(void) const { return mState; } + bool HasKeyRecord(void) const { return mKeyRecord.IsPresent(); } void Register(const Key &aKey, const Callback &aCallback); void Unregister(const Key &aKey); void InvokeCallbacks(void); void ClearAppendState(void); + Error CopyKeyInfoTo(Key &aKey, EntryState &aState) const; protected: static constexpr uint32_t kMinIntervalProbeResponse = 250; // msec @@ -891,6 +973,8 @@ private: void ClearAppendState(void); void PrepareResponse(TxMessage &aResponse, TimeMilli aNow); void HandleConflict(void); + Error CopyInfoTo(Host &aHost, EntryState &aState) const; + Error CopyInfoTo(Key &aKey, EntryState &aState) const; private: Error Init(Instance &aInstance, const char *aName); @@ -946,6 +1030,8 @@ private: void ClearAppendState(void); void PrepareResponse(TxMessage &aResponse, TimeMilli aNow); void HandleConflict(void); + Error CopyInfoTo(Service &aService, EntryState &aState, EntryIterator &aIterator) const; + Error CopyInfoTo(Key &aKey, EntryState &aState) const; private: class SubType : public LinkedListEntry, public Heap::Allocatable, private ot::NonCopyable @@ -1021,7 +1107,7 @@ private: public: Error Init(Instance &aInstance, const char *aServiceType); - bool Matches(const Name &aServcieTypeName) const; + bool Matches(const Name &aServiceTypeName) const; bool Matches(const Heap::String &aServiceType) const; bool Matches(const ServiceType &aServiceType) const { return (this == &aServiceType); } void IncrementNumEntries(void) { mNumEntries++; } @@ -1714,6 +1800,43 @@ private: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + class EntryIterator : public Iterator, public InstanceLocator, public Heap::Allocatable + { + friend class Heap::Allocatable; + friend class ServiceEntry; + + public: + Error GetNextHost(Host &aHost, EntryState &aState); + Error GetNextService(Service &aService, EntryState &aState); + Error GetNextKey(Key &aKey, EntryState &aState); + + private: + static constexpr uint16_t kArrayCapacityIncrement = 32; + + enum Type : uint8_t + { + kUnspecified, + kHost, + kService, + kHostKey, + kServiceKey, + }; + + explicit EntryIterator(Instance &aInstance); + + Type mType; + + union + { + const HostEntry *mHostEntry; + const ServiceEntry *mServiceEntry; + }; + + Heap::Array mSubTypeArray; + }; + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + template OwningList &GetEntryList(void); template Error Register(const ItemInfo &aItemInfo, RequestId aRequestId, RegisterCallback aCallback); @@ -1782,7 +1905,7 @@ private: CacheTask mCacheTask; }; -// Specializations of `Core::GetEntryList()` for `HostEntry` and `ServcieEntry`: +// Specializations of `Core::GetEntryList()` for `HostEntry` and `ServiceEntry`: template <> inline OwningList &Core::GetEntryList(void) { return mHostEntries; }