| /* |
| * Copyright (c) 2023, The OpenThread Authors. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the copyright holder nor the |
| * names of its contributors may be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "link_metrics_manager.hpp" |
| |
| #if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE |
| |
| #include "common/as_core_type.hpp" |
| #include "common/error.hpp" |
| #include "common/locator_getters.hpp" |
| #include "common/log.hpp" |
| #include "common/notifier.hpp" |
| #include "thread/mle.hpp" |
| #include "thread/neighbor_table.hpp" |
| |
| namespace ot { |
| namespace Utils { |
| |
| RegisterLogModule("LinkMetricsMgr"); |
| |
| /** |
| * @addtogroup utils-link-metrics-manager |
| * |
| * @brief |
| * This module includes definitions for Link Metrics Manager. |
| * |
| * @{ |
| */ |
| |
| LinkMetricsManager::LinkMetricsManager(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mTimer(aInstance) |
| , mEnabled(OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ON_BY_DEFAULT) |
| { |
| } |
| |
| void LinkMetricsManager::SetEnabled(bool aEnable) |
| { |
| VerifyOrExit(mEnabled != aEnable); |
| mEnabled = aEnable; |
| |
| if (mEnabled) |
| { |
| Start(); |
| } |
| else |
| { |
| Stop(); |
| } |
| |
| exit: |
| return; |
| } |
| |
| Error LinkMetricsManager::GetLinkMetricsValueByExtAddr(const Mac::ExtAddress &aExtAddress, |
| LinkMetrics::MetricsValues &aMetricsValues) |
| { |
| Error error = kErrorNone; |
| Subject *subject; |
| |
| subject = mSubjectList.FindMatching(aExtAddress); |
| VerifyOrExit(subject != nullptr, error = kErrorNotFound); |
| |
| aMetricsValues.mLinkMarginValue = subject->mData.mLinkMargin; |
| aMetricsValues.mRssiValue = subject->mData.mRssi; |
| |
| exit: |
| return error; |
| } |
| |
| void LinkMetricsManager::Start(void) |
| { |
| LinkMetrics::Initiator &initiator = Get<LinkMetrics::Initiator>(); |
| |
| VerifyOrExit(mEnabled && Get<Mle::Mle>().IsAttached()); |
| |
| initiator.SetMgmtResponseCallback(HandleMgmtResponse, this); |
| initiator.SetEnhAckProbingCallback(HandleEnhAckIe, this); |
| |
| mTimer.Start(kTimeBeforeStartMilliSec); |
| exit: |
| return; |
| } |
| |
| void LinkMetricsManager::Stop(void) |
| { |
| LinkMetrics::Initiator &initiator = Get<LinkMetrics::Initiator>(); |
| |
| mTimer.Stop(); |
| |
| initiator.SetMgmtResponseCallback(nullptr, nullptr); |
| initiator.SetEnhAckProbingCallback(nullptr, nullptr); |
| |
| UnregisterAllSubjects(); |
| ReleaseAllSubjects(); |
| } |
| |
| void LinkMetricsManager::Update(void) |
| { |
| UpdateSubjects(); |
| UpdateLinkMetricsStates(); |
| } |
| |
| // This method updates the Link Metrics Subject in the subject list. It adds new neighbors to the list. |
| void LinkMetricsManager::UpdateSubjects(void) |
| { |
| Neighbor::Info neighborInfo; |
| otNeighborInfoIterator iterator = OT_NEIGHBOR_INFO_ITERATOR_INIT; |
| |
| while (Get<NeighborTable>().GetNextNeighborInfo(iterator, neighborInfo) == kErrorNone) |
| { |
| // if not in the subject list, allocate and add |
| if (!mSubjectList.ContainsMatching(AsCoreType(&neighborInfo.mExtAddress))) |
| { |
| Subject *subject = mPool.Allocate(); |
| |
| VerifyOrExit(subject != nullptr); |
| |
| subject->Clear(); |
| subject->mExtAddress = AsCoreType(&neighborInfo.mExtAddress); |
| IgnoreError(mSubjectList.Add(*subject)); |
| } |
| } |
| |
| exit: |
| return; |
| } |
| |
| // This method updates the state and take corresponding actions for all subjects and removes stale subjects. |
| void LinkMetricsManager::UpdateLinkMetricsStates(void) |
| { |
| LinkedList<Subject> staleSubjects; |
| |
| mSubjectList.RemoveAllMatching(*this, staleSubjects); |
| |
| while (!staleSubjects.IsEmpty()) |
| { |
| Subject *subject = staleSubjects.Pop(); |
| |
| mPool.Free(*subject); |
| } |
| } |
| |
| void LinkMetricsManager::UnregisterAllSubjects(void) |
| { |
| for (Subject &subject : mSubjectList) |
| { |
| IgnoreError(subject.UnregisterEap(GetInstance())); |
| } |
| } |
| |
| void LinkMetricsManager::ReleaseAllSubjects(void) |
| { |
| while (!mSubjectList.IsEmpty()) |
| { |
| Subject *subject = mSubjectList.Pop(); |
| |
| mPool.Free(*subject); |
| } |
| } |
| |
| void LinkMetricsManager::HandleNotifierEvents(Events aEvents) |
| { |
| if (aEvents.Contains(kEventThreadRoleChanged)) |
| { |
| if (Get<Mle::Mle>().IsAttached()) |
| { |
| Start(); |
| } |
| else |
| { |
| Stop(); |
| } |
| } |
| } |
| |
| void LinkMetricsManager::HandleTimer(void) |
| { |
| if (Get<Mle::Mle>().IsAttached()) |
| { |
| Update(); |
| mTimer.Start(kStateUpdateIntervalMilliSec); |
| } |
| } |
| |
| void LinkMetricsManager::HandleMgmtResponse(const otIp6Address *aAddress, otLinkMetricsStatus aStatus, void *aContext) |
| { |
| static_cast<LinkMetricsManager *>(aContext)->HandleMgmtResponse(aAddress, aStatus); |
| } |
| |
| void LinkMetricsManager::HandleMgmtResponse(const otIp6Address *aAddress, otLinkMetricsStatus aStatus) |
| { |
| Mac::ExtAddress extAddress; |
| Subject *subject; |
| Neighbor *neighbor; |
| |
| AsCoreType(aAddress).GetIid().ConvertToExtAddress(extAddress); |
| neighbor = Get<NeighborTable>().FindNeighbor(extAddress); |
| VerifyOrExit(neighbor != nullptr); |
| |
| subject = mSubjectList.FindMatching(extAddress); |
| VerifyOrExit(subject != nullptr); |
| |
| switch (MapEnum(aStatus)) |
| { |
| case LinkMetrics::Status::kStatusSuccess: |
| subject->mState = SubjectState::kActive; |
| break; |
| default: |
| subject->mState = SubjectState::kNotConfigured; |
| break; |
| } |
| |
| exit: |
| return; |
| } |
| |
| void LinkMetricsManager::HandleEnhAckIe(otShortAddress aShortAddress, |
| const otExtAddress *aExtAddress, |
| const otLinkMetricsValues *aMetricsValues, |
| void *aContext) |
| { |
| static_cast<LinkMetricsManager *>(aContext)->HandleEnhAckIe(aShortAddress, aExtAddress, aMetricsValues); |
| } |
| |
| void LinkMetricsManager::HandleEnhAckIe(otShortAddress aShortAddress, |
| const otExtAddress *aExtAddress, |
| const otLinkMetricsValues *aMetricsValues) |
| { |
| OT_UNUSED_VARIABLE(aShortAddress); |
| |
| Error error = kErrorNone; |
| Subject *subject = mSubjectList.FindMatching(AsCoreType(aExtAddress)); |
| |
| VerifyOrExit(subject != nullptr, error = kErrorNotFound); |
| |
| VerifyOrExit(subject->mState == SubjectState::kActive || subject->mState == SubjectState::kRenewing); |
| subject->mLastUpdateTime = TimerMilli::GetNow(); |
| |
| VerifyOrExit(aMetricsValues->mMetrics.mRssi && aMetricsValues->mMetrics.mLinkMargin, error = kErrorInvalidArgs); |
| |
| subject->mData.mRssi = aMetricsValues->mRssiValue; |
| subject->mData.mLinkMargin = aMetricsValues->mLinkMarginValue; |
| |
| exit: |
| if (error == kErrorInvalidArgs) |
| { |
| LogWarn("Metrics received are unexpected!"); |
| } |
| } |
| |
| // This special Match method is used for "iterating over list while removing some items" |
| bool LinkMetricsManager::Subject::Matches(const LinkMetricsManager &aLinkMetricsMgr) |
| { |
| Error error = UpdateState(aLinkMetricsMgr.GetInstance()); |
| |
| return error == kErrorUnknownNeighbor || error == kErrorNotCapable; |
| } |
| |
| Error LinkMetricsManager::Subject::ConfigureEap(Instance &aInstance) |
| { |
| Error error = kErrorNone; |
| Neighbor *neighbor = aInstance.Get<NeighborTable>().FindNeighbor(mExtAddress); |
| Ip6::Address destination; |
| LinkMetrics::EnhAckFlags enhAckFlags = LinkMetrics::kEnhAckRegister; |
| LinkMetrics::Metrics metricsFlags; |
| |
| VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor); |
| destination.SetToLinkLocalAddress(neighbor->GetExtAddress()); |
| |
| metricsFlags.Clear(); |
| metricsFlags.mLinkMargin = 1; |
| metricsFlags.mRssi = 1; |
| error = |
| aInstance.Get<LinkMetrics::Initiator>().SendMgmtRequestEnhAckProbing(destination, enhAckFlags, &metricsFlags); |
| |
| exit: |
| if (error == kErrorNone) |
| { |
| mState = (mState == SubjectState::kActive) ? SubjectState::kRenewing : SubjectState::kConfiguring; |
| mAttempts++; |
| } |
| return error; |
| } |
| |
| Error LinkMetricsManager::Subject::UnregisterEap(Instance &aInstance) |
| { |
| Error error = kErrorNone; |
| Neighbor *neighbor = aInstance.Get<NeighborTable>().FindNeighbor(mExtAddress); |
| Ip6::Address destination; |
| LinkMetrics::EnhAckFlags enhAckFlags = LinkMetrics::kEnhAckClear; |
| |
| VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor); |
| destination.SetToLinkLocalAddress(neighbor->GetExtAddress()); |
| |
| error = aInstance.Get<LinkMetrics::Initiator>().SendMgmtRequestEnhAckProbing(destination, enhAckFlags, nullptr); |
| exit: |
| return error; |
| } |
| |
| Error LinkMetricsManager::Subject::UpdateState(Instance &aInstance) |
| { |
| bool shouldConfigure = false; |
| uint32_t pastTimeMs; |
| Error error = kErrorNone; |
| |
| switch (mState) |
| { |
| case kNotConfigured: |
| case kConfiguring: |
| case kRenewing: |
| if (mAttempts >= kConfigureLinkMetricsMaxAttempts) |
| { |
| mState = kNotSupported; |
| } |
| else |
| { |
| shouldConfigure = true; |
| } |
| break; |
| case kActive: |
| pastTimeMs = TimerMilli::GetNow() - mLastUpdateTime; |
| if (pastTimeMs >= kStateUpdateIntervalMilliSec) |
| { |
| shouldConfigure = true; |
| } |
| break; |
| case kNotSupported: |
| ExitNow(error = kErrorNotCapable); |
| break; |
| default: |
| break; |
| } |
| |
| if (shouldConfigure) |
| { |
| error = ConfigureEap(aInstance); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| /** |
| * @} |
| * |
| */ |
| |
| } // namespace Utils |
| } // namespace ot |
| |
| #endif // OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE |