/* * Copyright (c) 2020, 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.h" #include #include "common/clearable.hpp" #include "common/linked_list.hpp" #include "common/pool.hpp" #include "thread/link_quality.hpp" #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE using namespace ot; static int8_t sNoiseFloor; ///< The noise floor used by Link Metrics. It should be set to the platform's ///< noise floor (measured noise floor, receiver sensitivity or a constant). class LinkMetricsDataInfo : public LinkedListEntry, public Clearable { friend class LinkedList; friend class LinkedListEntry; public: /** * Constructor. * */ LinkMetricsDataInfo(void) { Clear(); }; /** * Set the information for this object. * * @param[in] aLinkMetrics Flags specifying what metrics to query. * @param[in] aShortAddress Short Address of the Probing Initiator tracked by this object. * @param[in] aExtAddress A reference to the Extended Address of the Probing Initiator tracked by this * object. * */ void Set(otLinkMetrics aLinkMetrics, otShortAddress aShortAddress, const otExtAddress &aExtAddress) { mLinkMetrics = aLinkMetrics; mShortAddress = aShortAddress; memcpy(mExtAddress.m8, aExtAddress.m8, sizeof(aExtAddress)); } /** * Gets Link Metrics data stored in this object. * * TODO: Currently the order of Link Metircs data is fixed. Will update it to follow the order specified in TLV. * * @param[in] aLqi LQI value of the acknowledeged frame. * @param[in] aRssi RSSI value of the acknowledged frame. * @param[out] aData A pointer to the output buffer. @p aData MUST NOT be `nullptr`. The buffer must have * at least 2 bytes (per spec 4.11.3.4.4.6). Otherwise the behavior would be undefined. * * @returns The number of bytes written. `0` on failure. * */ uint8_t GetEnhAckData(uint8_t aLqi, int8_t aRssi, uint8_t *aData) const { enum { kEnhAckProbingDataMaxLen = 2, }; uint8_t bytes = 0; VerifyOrExit(aData != nullptr); if (mLinkMetrics.mLqi) { aData[bytes++] = aLqi; } if (mLinkMetrics.mLinkMargin) { aData[bytes++] = static_cast(GetLinkMargin(aRssi) * 255 / 130); // Linear scale Link Margin from [0, 130] to [0, 255] } if (bytes < kEnhAckProbingDataMaxLen && mLinkMetrics.mRssi) { aData[bytes++] = static_cast((aRssi + 130) * 255 / 130); // Linear scale RSSI from [-130, 0] to [0, 255] } exit: return bytes; } /** * Gets the length of Link Metrics Data. * * @returns The number of bytes for the data. * */ uint8_t GetEnhAckDataLen() const { return static_cast(mLinkMetrics.mLqi) + static_cast(mLinkMetrics.mLinkMargin) + static_cast(mLinkMetrics.mRssi); } /** * Gets the metrics configured for the Enhanced-ACK Based Probing. * * @returns The metrics configured. * */ otLinkMetrics GetLinkMetrics(void) const { return mLinkMetrics; } private: uint8_t GetLinkMargin(int8_t aRssi) const { return ComputeLinkMargin(sNoiseFloor, aRssi); } bool Matches(const otShortAddress &aShortAddress) const { return mShortAddress == aShortAddress; }; bool Matches(const otExtAddress &aExtAddress) const { return memcmp(&mExtAddress, &aExtAddress, sizeof(otExtAddress)) == 0; }; LinkMetricsDataInfo *mNext; otLinkMetrics mLinkMetrics; otShortAddress mShortAddress; otExtAddress mExtAddress; }; enum { kMaxEnhAckProbingInitiator = OPENTHREAD_CONFIG_MLE_LINK_METRICS_MAX_SERIES_SUPPORTED, }; typedef Pool LinkMetricsDataInfoPool; typedef LinkedList LinkMetricsDataInfoList; static LinkMetricsDataInfoPool &GetLinkMetricsDataInfoPool(void) { static LinkMetricsDataInfoPool sDataInfoPool; return sDataInfoPool; } static LinkMetricsDataInfoList &GetLinkMetricsDataInfoActiveList(void) { static LinkMetricsDataInfoList sDataInfoActiveList; return sDataInfoActiveList; } static inline bool IsLinkMetricsClear(otLinkMetrics aLinkMetrics) { return !aLinkMetrics.mPduCount && !aLinkMetrics.mLqi && !aLinkMetrics.mLinkMargin && !aLinkMetrics.mRssi; } void otLinkMetricsInit(int8_t aNoiseFloor) { sNoiseFloor = aNoiseFloor; otLinkMetricsResetEnhAckProbing(); } otError otLinkMetricsConfigureEnhAckProbing(otShortAddress aShortAddress, const otExtAddress *aExtAddress, otLinkMetrics aLinkMetrics) { otError error = OT_ERROR_NONE; LinkMetricsDataInfo *dataInfo = nullptr; VerifyOrExit(aExtAddress != nullptr, error = OT_ERROR_INVALID_ARGS); if (IsLinkMetricsClear(aLinkMetrics)) ///< Remove the entry { dataInfo = GetLinkMetricsDataInfoActiveList().RemoveMatching(aShortAddress); VerifyOrExit(dataInfo != nullptr, error = OT_ERROR_NOT_FOUND); GetLinkMetricsDataInfoPool().Free(*dataInfo); } else { dataInfo = GetLinkMetricsDataInfoActiveList().FindMatching(aShortAddress); if (dataInfo == nullptr) { dataInfo = GetLinkMetricsDataInfoPool().Allocate(); VerifyOrExit(dataInfo != nullptr, error = OT_ERROR_NO_BUFS); dataInfo->Clear(); GetLinkMetricsDataInfoActiveList().Push(*dataInfo); } // Overwrite the previous configuration if it already existed. dataInfo->Set(aLinkMetrics, aShortAddress, *aExtAddress); } exit: return error; } LinkMetricsDataInfo *GetLinkMetricsInfoByMacAddress(const otMacAddress *aMacAddress) { LinkMetricsDataInfo *dataInfo = nullptr; VerifyOrExit(aMacAddress != nullptr); if (aMacAddress->mType == OT_MAC_ADDRESS_TYPE_SHORT) { dataInfo = GetLinkMetricsDataInfoActiveList().FindMatching(aMacAddress->mAddress.mShortAddress); } else if (aMacAddress->mType == OT_MAC_ADDRESS_TYPE_EXTENDED) { dataInfo = GetLinkMetricsDataInfoActiveList().FindMatching(aMacAddress->mAddress.mExtAddress); } exit: return dataInfo; } uint8_t otLinkMetricsEnhAckGenData(const otMacAddress *aMacAddress, uint8_t aLqi, int8_t aRssi, uint8_t *aData) { uint8_t bytes = 0; LinkMetricsDataInfo *dataInfo = GetLinkMetricsInfoByMacAddress(aMacAddress); VerifyOrExit(dataInfo != nullptr); bytes = dataInfo->GetEnhAckData(aLqi, aRssi, aData); exit: return bytes; } uint8_t otLinkMetricsEnhAckGetDataLen(const otMacAddress *aMacAddress) { uint8_t len = 0; LinkMetricsDataInfo *dataInfo = GetLinkMetricsInfoByMacAddress(aMacAddress); VerifyOrExit(dataInfo != nullptr); len = dataInfo->GetEnhAckDataLen(); exit: return len; } void otLinkMetricsResetEnhAckProbing(void) { GetLinkMetricsDataInfoActiveList().Clear(); GetLinkMetricsDataInfoPool().FreeAll(); } #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE