From 42fb2e44be6e46c2f62bcac121f24f01417ad314 Mon Sep 17 00:00:00 2001 From: Li Cao Date: Fri, 30 Oct 2020 11:04:39 +0800 Subject: [PATCH] [low-power] implement forward tracking series (#5608) This commit implements the Forward Tracking Series feature in Link Metrics. - Two new apis are added: - otLinkMetricsSendMgmtRequestForwardTrackingSeries, which is used to send an MLE Link Metrics Management Request - otLinkMetricsSendLinkProbe, which is used to send an MLE Link Probe - A new class LinkMetricsSeriesInfo is used to maintain the data of one Series configured by a neighbor. This class inherits LinkedListEntry and each Neighbor has a list of LinkMetricsSeriesInfo. All LinkedListEntrys are allocated and freed by a Pool in LinkMetrics. - To specify SeriesFlags in cli command for Forward Tracking Series, a similar approach with LinkMetricsFlags is used. Another character flags is used to represent SeriesFlags. - Whenever the node receives a frame (including ACK) from a neighbor, it would call Neighbor::AggregateLinkMetrics which goes through each LinkMetricsSeriesInfo entry in the neighbor's list and aggregate the data if the frame type matches the entry. - Two test scripts are added to test this function: - v1_2_LowPower_7_2_01_ForwardTrackingSeries.py - v1_2_LowPower_test_forward_tracking_series.py --- include/openthread/instance.h | 2 +- include/openthread/link_metrics.h | 85 ++- script/check-size | 1 + src/cli/README.md | 58 +- src/cli/cli.cpp | 270 +++++-- src/cli/cli.hpp | 15 +- src/core/api/link_metrics_api.cpp | 32 +- src/core/config/mle.h | 10 + src/core/mac/mac.cpp | 7 + src/core/thread/link_metrics.cpp | 659 ++++++++++++++---- src/core/thread/link_metrics.hpp | 251 ++++++- src/core/thread/link_metrics_tlvs.hpp | 144 +++- src/core/thread/mle.cpp | 150 ++++ src/core/thread/mle.hpp | 92 ++- src/core/thread/mle_router.cpp | 7 +- src/core/thread/mle_tlvs.hpp | 64 +- src/core/thread/topology.cpp | 39 ++ src/core/thread/topology.hpp | 60 ++ tests/scripts/thread-cert/config.py | 2 + tests/scripts/thread-cert/mle.py | 40 ++ tests/scripts/thread-cert/node.py | 16 + tests/scripts/thread-cert/pktverify/consts.py | 19 + .../thread-cert/pktverify/layer_fields.py | 7 + ...2_LowPower_7_2_01_ForwardTrackingSeries.py | 340 +++++++++ ...2_LowPower_test_forward_tracking_series.py | 194 ++++++ 25 files changed, 2299 insertions(+), 265 deletions(-) create mode 100755 tests/scripts/thread-cert/v1_2_LowPower_7_2_01_ForwardTrackingSeries.py create mode 100755 tests/scripts/thread-cert/v1_2_LowPower_test_forward_tracking_series.py diff --git a/include/openthread/instance.h b/include/openthread/instance.h index c22a828f0..92c6cc747 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 (42) +#define OPENTHREAD_API_VERSION (43) /** * @addtogroup api-instance diff --git a/include/openthread/link_metrics.h b/include/openthread/link_metrics.h index 5209923ea..75f45da6b 100644 --- a/include/openthread/link_metrics.h +++ b/include/openthread/link_metrics.h @@ -54,7 +54,7 @@ extern "C" { * */ -/* +/** * This structure represents what metrics are specified to query. * */ @@ -66,7 +66,7 @@ typedef struct otLinkMetrics bool mRssi : 1; } otLinkMetrics; -/* +/** * This structure represents the result (value) for a Link Metrics query. * */ @@ -80,17 +80,54 @@ typedef struct otLinkMetricsValues int8_t mRssiValue; ///< The value of Rssi. } otLinkMetricsValues; +/** + * This structure represents which frames are accounted in a Forward Tracking Series. + * + */ +typedef struct otLinkMetricsSeriesFlags +{ + bool mLinkProbe : 1; ///< MLE Link Probe. + bool mMacData : 1; ///< MAC Data frame. + bool mMacDataRequest : 1; ///< MAC Data Request. + bool mMacAck : 1; ///< MAC Ack. +} otLinkMetricsSeriesFlags; + +/** + * Link Metrics Status values. + * + */ +typedef enum otLinkMetricsStatus : uint8_t +{ + OT_LINK_METRICS_STATUS_SUCCESS = 0, + OT_LINK_METRICS_STATUS_CANNOT_SUPPORT_NEW_SERIES = 1, + OT_LINK_METRICS_STATUS_SERIESID_ALREADY_REGISTERED = 2, + OT_LINK_METRICS_STATUS_SERIESID_NOT_RECOGNIZED = 3, + OT_LINK_METRICS_STATUS_NO_MATCHING_FRAMES_RECEIVED = 4, + OT_LINK_METRICS_STATUS_OTHER_ERROR = 254, +} otLinkMetricsStatus; + /** * This function pointer is called when a Link Metrics report is received. * * @param[in] aSource A pointer to the source address. * @param[in] aMetricsValues A pointer to the Link Metrics values (the query result). + * @param[in] aStatus The status code in the report (only useful when @p aMetricsValues is NULL). * @param[in] aContext A pointer to application-specific context. * */ typedef void (*otLinkMetricsReportCallback)(const otIp6Address * aSource, const otLinkMetricsValues *aMetricsValues, + uint8_t aStatus, void * aContext); +/** + * This function pointer is called when a Link Metrics Management Response is received. + * + * @param[in] aSource A pointer to the source address. + * @param[in] aStatus The status code in the response. + * @param[in] aContext A pointer to application-specific context. + * + */ +typedef void (*otLinkMetricsMgmtResponseCallback)(const otIp6Address *aSource, uint8_t aStatus, void *aContext); /** * This function sends an MLE Data Request to query Link Metrics. @@ -115,6 +152,50 @@ otError otLinkMetricsQuery(otInstance * aInstance, otLinkMetricsReportCallback aCallback, void * aCallbackContext); +/** + * This function sends an MLE Link Metrics Management Request to configure/clear a Forward Tracking Series. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aDestination A pointer to the destination address. + * @param[in] aSeriesId The Series ID to operate with. + * @param[in] aSeriesFlags The Series Flags that specifies which frames are to be accounted. + * @param[in] aLinkMetricsFlags A pointer to flags specifying what metrics to query. Should be `NULL` when + * `aSeriesFlags` is `0`. + * @param[in] aCallback A pointer to a function that is called when Link Metrics Management Response is + * received. + * @param[in] aCallbackContext A pointer to application-specific context. + * + * @retval OT_ERROR_NONE Successfully sent a Link Metrics Management Request message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Metrics Management Request message. + * @retval OT_ERROR_INVALID_ARGS @p aSeriesId is not within the valid range. + * + */ +otError otLinkMetricsConfigForwardTrackingSeries(otInstance * aInstance, + const otIp6Address * aDestination, + uint8_t aSeriesId, + otLinkMetricsSeriesFlags aSeriesFlags, + const otLinkMetrics * aLinkMetricsFlags, + otLinkMetricsMgmtResponseCallback aCallback, + void * aCallbackContext); + +/** + * This function sends an MLE Link Probe message. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aDestination A pointer to the destination address. + * @param[in] aSeriesId The Series ID [1, 254] which the Probe message aims at. + * @param[in] aLength The length of the data payload in Link Probe TLV, [0, 64] (per Thread 1.2 spec, 4.4.37). + * + * @retval OT_ERROR_NONE Successfully sent a Link Probe message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Probe message. + * @retval OT_ERROR_INVALID_ARGS @p aSeriesId or @p aLength is not within the valid range. + * + */ +otError otLinkMetricsSendLinkProbe(otInstance * aInstance, + const otIp6Address *aDestination, + uint8_t aSeriesId, + uint8_t aLength); + /** * @} * diff --git a/script/check-size b/script/check-size index 941b0effe..4387e5432 100755 --- a/script/check-size +++ b/script/check-size @@ -137,6 +137,7 @@ size_nrf52840_version() "DUA=1" "MLR=1" "CSL_RECEIVER=1" + "LINK_METRICS=1" ) fi diff --git a/src/cli/README.md b/src/cli/README.md index 0eb40fb56..d0cd2fc0c 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -59,7 +59,7 @@ Done - [leaderdata](#leaderdata) - [leaderpartitionid](#leaderpartitionid) - [leaderweight](#leaderweight) -- [linkmetrics](#linkmetrics-query-ipaddr-single-pqmr) +- [linkmetrics](#linkmetrics-mgmt-ipaddr-forward-seriesid-ldraxpqmr) - [linkquality](#linkquality-extaddr) - [log](#log-filename-filename) - [mac](#mac-retries-direct) @@ -1143,6 +1143,44 @@ Set the Thread Leader Weight. Done ``` +### linkmetrics mgmt \ forward \ [ldraX][pqmr] + +Send a Link Metrics Management Request to configure a Forward Tracking Series. + +- ipaddr: Peer address. +- seriesid: The Series ID. +- ldraX: This specifies which frames are to be accounted. + - l: MLE Link Probe. + - d: MAC Data. + - r: MAC Data Request. + - a: MAC Ack. + - X: This represents none of the above flags, i.e., to stop accounting and remove the series. This can only be used without any other flags. +- pqmr: This specifies what metrics to query. + - p: Layer 2 Number of PDUs received. + - q: Layer 2 LQI. + - m: Link Margin. + - r: RSSI. + +```bash +> linkmetrics mgmt fe80:0:0:0:3092:f334:1455:1ad2 forward 1 dra pqmr +Done +> Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2 +Status: SUCCESS +``` + +### linkmetrics probe \ \ \ + +Send a MLE Link Probe message to the peer. + +- ipaddr: Peer address. +- seriesid: The Series ID for which this Probe message targets at. +- length: The length of the Probe message, valid range: [0, 64]. + +```bash +> linkmetrics probe fe80:0:0:0:3092:f334:1455:1ad2 1 10 +Done +``` + ### linkmetrics query \ single [pqmr] Perform a Link Metrics query (Single Probe). @@ -1164,6 +1202,24 @@ Done - RSSI: -18 (dBm) (Exponential Moving Average) ``` +### linkmetrics query \ forward \ + +Perform a Link Metrics query (Forward Tracking Series). + +- ipaddr: Peer address. +- seriesid: The Series ID. + +```bash +> linkmetrics query fe80:0:0:0:3092:f334:1455:1ad2 forward 1 +Done +> Received Link Metrics Report from: fe80:0:0:0:3092:f334:1455:1ad2 + + - PDU Counter: 2 (Count/Summation) + - LQI: 76 (Exponential Moving Average) + - Margin: 82 (dB) (Exponential Moving Average) + - RSSI: -18 (dBm) (Exponential Moving Average) +``` + ### linkquality \ Get the link quality on the link to a given extended address. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index c2c0634d9..3ae860970 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -1904,12 +1904,15 @@ exit: #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE void Interpreter::HandleLinkMetricsReport(const otIp6Address * aAddress, const otLinkMetricsValues *aMetricsValues, + uint8_t aStatus, void * aContext) { - static_cast(aContext)->HandleLinkMetricsReport(aAddress, aMetricsValues); + static_cast(aContext)->HandleLinkMetricsReport(aAddress, aMetricsValues, aStatus); } -void Interpreter::HandleLinkMetricsReport(const otIp6Address *aAddress, const otLinkMetricsValues *aMetricsValues) +void Interpreter::HandleLinkMetricsReport(const otIp6Address * aAddress, + const otLinkMetricsValues *aMetricsValues, + uint8_t aStatus) { const char kLinkMetricsTypeCount[] = "(Count/Summation)"; const char kLinkMetricsTypeAverage[] = "(Exponential Moving Average)"; @@ -1918,25 +1921,87 @@ void Interpreter::HandleLinkMetricsReport(const otIp6Address *aAddress, const ot OutputIp6Address(*aAddress); OutputLine(""); - if (aMetricsValues->mMetrics.mPduCount) + if (aMetricsValues != nullptr) { - OutputLine(" - PDU Counter: %d %s", aMetricsValues->mPduCountValue, kLinkMetricsTypeCount); + if (aMetricsValues->mMetrics.mPduCount) + { + OutputLine(" - PDU Counter: %d %s", aMetricsValues->mPduCountValue, kLinkMetricsTypeCount); + } + + if (aMetricsValues->mMetrics.mLqi) + { + OutputLine(" - LQI: %d %s", aMetricsValues->mLqiValue, kLinkMetricsTypeAverage); + } + + if (aMetricsValues->mMetrics.mLinkMargin) + { + OutputLine(" - Margin: %d (dB) %s", aMetricsValues->mLinkMarginValue, kLinkMetricsTypeAverage); + } + + if (aMetricsValues->mMetrics.mRssi) + { + OutputLine(" - RSSI: %d (dBm) %s", aMetricsValues->mRssiValue, kLinkMetricsTypeAverage); + } + } + else + { + OutputLine("Link Metrics Report, status: %s", LinkMetricsStatusToStr(aStatus)); + } +} + +void Interpreter::HandleLinkMetricsMgmtResponse(const otIp6Address *aAddress, uint8_t aStatus, void *aContext) +{ + static_cast(aContext)->HandleLinkMetricsMgmtResponse(aAddress, aStatus); +} + +void Interpreter::HandleLinkMetricsMgmtResponse(const otIp6Address *aAddress, uint8_t aStatus) +{ + OutputFormat("Received Link Metrics Management Response from: "); + OutputIp6Address(*aAddress); + OutputLine(""); + + OutputLine("Status: %s", LinkMetricsStatusToStr(aStatus)); +} + +const char *Interpreter::LinkMetricsStatusToStr(uint8_t aStatus) +{ + uint8_t strIndex = 0; + static const char *linkMetricsStatusText[] = { + "Success", + "Cannot support new series", + "Series ID already registered", + "Series ID not recognized", + "No matching series ID", + "Other error", + "Unknown error", + }; + + switch (aStatus) + { + case OT_LINK_METRICS_STATUS_SUCCESS: + strIndex = 0; + break; + case OT_LINK_METRICS_STATUS_CANNOT_SUPPORT_NEW_SERIES: + strIndex = 1; + break; + case OT_LINK_METRICS_STATUS_SERIESID_ALREADY_REGISTERED: + strIndex = 2; + break; + case OT_LINK_METRICS_STATUS_SERIESID_NOT_RECOGNIZED: + strIndex = 3; + break; + case OT_LINK_METRICS_STATUS_NO_MATCHING_FRAMES_RECEIVED: + strIndex = 4; + break; + case OT_LINK_METRICS_STATUS_OTHER_ERROR: + strIndex = 5; + break; + default: + strIndex = 6; + break; } - if (aMetricsValues->mMetrics.mLqi) - { - OutputLine(" - LQI: %d %s", aMetricsValues->mLqiValue, kLinkMetricsTypeAverage); - } - - if (aMetricsValues->mMetrics.mLinkMargin) - { - OutputLine(" - Margin: %d (dB) %s", aMetricsValues->mLinkMarginValue, kLinkMetricsTypeAverage); - } - - if (aMetricsValues->mMetrics.mRssi) - { - OutputLine(" - RSSI: %d (dBm) %s", aMetricsValues->mRssiValue, kLinkMetricsTypeAverage); - } + return linkMetricsStatusText[strIndex]; } otError Interpreter::ProcessLinkMetrics(uint8_t aArgsLength, char *aArgs[]) @@ -1949,6 +2014,14 @@ otError Interpreter::ProcessLinkMetrics(uint8_t aArgsLength, char *aArgs[]) { error = ProcessLinkMetricsQuery(aArgsLength - 1, aArgs + 1); } + else if (strcmp(aArgs[0], "mgmt") == 0) + { + error = ProcessLinkMetricsMgmt(aArgsLength - 1, aArgs + 1); + } + else if (strcmp(aArgs[0], "probe") == 0) + { + error = ProcessLinkMetricsProbe(aArgsLength - 1, aArgs + 1); + } exit: return error; @@ -1959,49 +2032,150 @@ otError Interpreter::ProcessLinkMetricsQuery(uint8_t aArgsLength, char *aArgs[]) otError error = OT_ERROR_INVALID_ARGS; otIp6Address address; otLinkMetrics linkMetrics; - long seriesId = 0; - VerifyOrExit(aArgsLength >= 2); + VerifyOrExit(aArgsLength == 3); SuccessOrExit(error = ParseAsIp6Address(aArgs[0], address)); - memset(&linkMetrics, 0, sizeof(otLinkMetrics)); - if (strcmp(aArgs[1], "single") == 0) { - VerifyOrExit(aArgsLength == 3); - for (char *arg = aArgs[2]; *arg != '\0'; arg++) - { - switch (*arg) - { - case 'p': - linkMetrics.mPduCount = true; - break; - - case 'q': - linkMetrics.mLqi = true; - break; - - case 'm': - linkMetrics.mLinkMargin = true; - break; - - case 'r': - linkMetrics.mRssi = true; - break; - - default: - ExitNow(error = OT_ERROR_INVALID_ARGS); - } - } - error = otLinkMetricsQuery(mInstance, &address, static_cast(seriesId), &linkMetrics, + SuccessOrExit(error = ParseLinkMetricsFlags(linkMetrics, aArgs[2])); + error = otLinkMetricsQuery(mInstance, &address, /* aSeriesId */ 0, &linkMetrics, &Interpreter::HandleLinkMetricsReport, this); } + else if (strcmp(aArgs[1], "forward") == 0) + { + uint8_t seriesId; + SuccessOrExit(error = ParseAsUint8(aArgs[2], seriesId)); + error = otLinkMetricsQuery(mInstance, &address, seriesId, nullptr, &Interpreter::HandleLinkMetricsReport, this); + } exit: return error; } +otError Interpreter::ParseLinkMetricsFlags(otLinkMetrics &aLinkMetrics, char *aFlags) +{ + otError error = OT_ERROR_NONE; + + memset(&aLinkMetrics, 0, sizeof(aLinkMetrics)); + + for (char *arg = aFlags; *arg != '\0'; arg++) + { + switch (*arg) + { + case 'p': + aLinkMetrics.mPduCount = true; + break; + + case 'q': + aLinkMetrics.mLqi = true; + break; + + case 'm': + aLinkMetrics.mLinkMargin = true; + break; + + case 'r': + aLinkMetrics.mRssi = true; + break; + + default: + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + } + +exit: + return error; +} + +otError Interpreter::ProcessLinkMetricsMgmt(uint8_t aArgsLength, char *aArgs[]) +{ + otError error = OT_ERROR_INVALID_ARGS; + otIp6Address address; + otLinkMetricsSeriesFlags seriesFlags; + bool clear = false; + + VerifyOrExit(aArgsLength >= 2); + + SuccessOrExit(error = ParseAsIp6Address(aArgs[0], address)); + + memset(&seriesFlags, 0, sizeof(otLinkMetricsSeriesFlags)); + + if (strcmp(aArgs[1], "forward") == 0) + { + uint8_t seriesId; + otLinkMetrics linkMetrics; + + VerifyOrExit(aArgsLength >= 4); + + memset(&linkMetrics, 0, sizeof(otLinkMetrics)); + SuccessOrExit(error = ParseAsUint8(aArgs[2], seriesId)); + + for (char *arg = aArgs[3]; *arg != '\0'; arg++) + { + switch (*arg) + { + case 'l': + seriesFlags.mLinkProbe = true; + break; + + case 'd': + seriesFlags.mMacData = true; + break; + + case 'r': + seriesFlags.mMacDataRequest = true; + break; + + case 'a': + seriesFlags.mMacAck = true; + break; + + case 'X': + VerifyOrExit(arg == aArgs[3] && *(arg + 1) == '\0' && aArgsLength == 4, + error = OT_ERROR_INVALID_ARGS); // Ensure the flags only contain 'X' + clear = true; + break; + + default: + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + } + + if (!clear) + { + VerifyOrExit(aArgsLength == 5); + SuccessOrExit(error = ParseLinkMetricsFlags(linkMetrics, aArgs[4])); + } + + error = otLinkMetricsConfigForwardTrackingSeries(mInstance, &address, seriesId, seriesFlags, + clear ? nullptr : &linkMetrics, + &Interpreter::HandleLinkMetricsMgmtResponse, this); + } + +exit: + return error; +} + +otError Interpreter::ProcessLinkMetricsProbe(uint8_t aArgsLength, char *aArgs[]) +{ + otError error = OT_ERROR_INVALID_ARGS; + otIp6Address address; + uint8_t seriesId = 0; + uint8_t length = 0; + + VerifyOrExit(aArgsLength == 3); + + SuccessOrExit(error = ParseAsIp6Address(aArgs[0], address)); + SuccessOrExit(error = ParseAsUint8(aArgs[1], seriesId)); + SuccessOrExit(error = ParseAsUint8(aArgs[2], length)); + + error = otLinkMetricsSendLinkProbe(mInstance, &address, seriesId, length); + +exit: + return error; +} #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE #if OPENTHREAD_FTD diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 392497fbb..7331b96cd 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -358,6 +358,10 @@ private: #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE otError ProcessLinkMetrics(uint8_t aArgsLength, char *aArgs[]); otError ProcessLinkMetricsQuery(uint8_t aArgsLength, char *aArgs[]); + otError ProcessLinkMetricsMgmt(uint8_t aArgsLength, char *aArgs[]); + otError ProcessLinkMetricsProbe(uint8_t aArgsLength, char *aArgs[]); + + otError ParseLinkMetricsFlags(otLinkMetrics &aLinkMetrics, char *aFlags); #endif #if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE otError ProcessMlr(uint8_t aArgsLength, char *aArgs[]); @@ -526,9 +530,18 @@ private: #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE static void HandleLinkMetricsReport(const otIp6Address * aAddress, const otLinkMetricsValues *aMetricsValues, + uint8_t aStatus, void * aContext); - void HandleLinkMetricsReport(const otIp6Address *aAddress, const otLinkMetricsValues *aMetricsValues); + void HandleLinkMetricsReport(const otIp6Address * aAddress, + const otLinkMetricsValues *aMetricsValues, + uint8_t aStatus); + + static void HandleLinkMetricsMgmtResponse(const otIp6Address *aAddress, uint8_t aStatus, void *aContext); + + void HandleLinkMetricsMgmtResponse(const otIp6Address *aAddress, uint8_t aStatus); + + const char *LinkMetricsStatusToStr(uint8_t aStatus); #endif static Interpreter &GetOwner(OwnerLocator &aOwnerLocator); diff --git a/src/core/api/link_metrics_api.cpp b/src/core/api/link_metrics_api.cpp index 80769f200..62fd7994e 100644 --- a/src/core/api/link_metrics_api.cpp +++ b/src/core/api/link_metrics_api.cpp @@ -49,12 +49,40 @@ otError otLinkMetricsQuery(otInstance * aInstance, otLinkMetricsReportCallback aCallback, void * aCallbackContext) { - OT_ASSERT(aDestination != nullptr && aLinkMetricsFlags != nullptr); + OT_ASSERT(aDestination != nullptr); static_cast(aInstance)->Get().SetLinkMetricsReportCallback(aCallback, aCallbackContext); return static_cast(aInstance)->Get().LinkMetricsQuery( - static_cast(*aDestination), aSeriesId, *aLinkMetricsFlags); + static_cast(*aDestination), aSeriesId, aLinkMetricsFlags); +} + +otError otLinkMetricsConfigForwardTrackingSeries(otInstance * aInstance, + const otIp6Address * aDestination, + uint8_t aSeriesId, + const otLinkMetricsSeriesFlags aSeriesFlags, + const otLinkMetrics * aLinkMetricsFlags, + otLinkMetricsMgmtResponseCallback aCallback, + void * aCallbackContext) +{ + OT_ASSERT(aDestination != nullptr); + + static_cast(aInstance)->Get().SetLinkMetricsMgmtResponseCallback(aCallback, + aCallbackContext); + + return static_cast(aInstance)->Get().SendMgmtRequestForwardTrackingSeries( + static_cast(*aDestination), aSeriesId, aSeriesFlags, aLinkMetricsFlags); +} + +otError otLinkMetricsSendLinkProbe(otInstance * aInstance, + const otIp6Address *aDestination, + uint8_t aSeriesId, + uint8_t aLength) +{ + OT_ASSERT(aDestination != nullptr); + + return static_cast(aInstance)->Get().SendLinkProbe( + static_cast(*aDestination), aSeriesId, aLength); } #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE diff --git a/src/core/config/mle.h b/src/core/config/mle.h index 4c5b5432d..5c2ee78a0 100644 --- a/src/core/config/mle.h +++ b/src/core/config/mle.h @@ -250,4 +250,14 @@ #define OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_MLE_LINK_METRICS_MAX_SERIES_SUPPORTED + * + * The max number of series that a Link Metrics Subject can track simultaneously. + * + */ +#ifndef OPENTHREAD_CONFIG_MLE_LINK_METRICS_MAX_SERIES_SUPPORTED +#define OPENTHREAD_CONFIG_MLE_LINK_METRICS_MAX_SERIES_SUPPORTED 10 +#endif + #endif // CONFIG_MLE_H_ diff --git a/src/core/mac/mac.cpp b/src/core/mac/mac.cpp index abe7010d4..bccf6a7e6 100644 --- a/src/core/mac/mac.cpp +++ b/src/core/mac/mac.cpp @@ -1298,6 +1298,10 @@ void Mac::RecordFrameTransmitStatus(const TxFrame &aFrame, if ((aError == OT_ERROR_NONE) && ackRequested && (aAckFrame != nullptr) && (neighbor != nullptr)) { neighbor->GetLinkInfo().AddRss(aAckFrame->GetRssi()); +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + neighbor->AggregateLinkMetrics(/* aSeriesId */ 0, aAckFrame->GetType(), aAckFrame->GetLqi(), + aAckFrame->GetRssi()); +#endif #if OPENTHREAD_FTD if (aAckFrame->GetVersion() == Frame::kFcfFrameVersion2015) { @@ -1903,6 +1907,9 @@ void Mac::HandleReceivedFrame(RxFrame *aFrame, otError aError) if (neighbor != nullptr) { neighbor->GetLinkInfo().AddRss(aFrame->GetRssi()); +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + neighbor->AggregateLinkMetrics(/* aSeriesId */ 0, aFrame->GetType(), aFrame->GetLqi(), aFrame->GetRssi()); +#endif if (aFrame->GetSecurityEnabled()) { diff --git a/src/core/thread/link_metrics.cpp b/src/core/thread/link_metrics.cpp index 7efc700a9..2f6015ae9 100644 --- a/src/core/thread/link_metrics.cpp +++ b/src/core/thread/link_metrics.cpp @@ -44,39 +44,182 @@ namespace ot { +void LinkMetricsSeriesInfo::Init(uint8_t aSeriesId, + const SeriesFlags & aSeriesFlags, + const otLinkMetrics &aLinkMetricsFlags) +{ + mSeriesId = aSeriesId; + mSeriesFlags = aSeriesFlags; + mLinkMetrics.mPduCount = aLinkMetricsFlags.mPduCount; + mLinkMetrics.mLqi = aLinkMetricsFlags.mLqi; + mLinkMetrics.mLinkMargin = aLinkMetricsFlags.mLinkMargin; + mLinkMetrics.mRssi = aLinkMetricsFlags.mRssi; + mRssAverager.Reset(); + mLqiAverager.Reset(); + mPduCount = 0; +} + +void LinkMetricsSeriesInfo::AggregateLinkMetrics(uint8_t aFrameType, uint8_t aLqi, int8_t aRss) +{ + if (IsFrameTypeMatch(aFrameType)) + { + mPduCount++; + mLqiAverager.Add(aLqi); + IgnoreError(mRssAverager.Add(aRss)); + } +} + +void LinkMetricsSeriesInfo::GetLinkMetrics(otLinkMetrics &aLinkMetrics) const +{ + aLinkMetrics.mPduCount = mLinkMetrics.mPduCount; + aLinkMetrics.mLqi = mLinkMetrics.mLqi; + aLinkMetrics.mLinkMargin = mLinkMetrics.mLinkMargin; + aLinkMetrics.mRssi = mLinkMetrics.mRssi; +} + +bool LinkMetricsSeriesInfo::IsFrameTypeMatch(uint8_t aFrameType) const +{ + bool match = false; + + switch (aFrameType) + { + case kSeriesTypeLinkProbe: + VerifyOrExit(!mSeriesFlags.IsMacDataFlagSet()); // Ignore this when Mac Data is accounted + match = mSeriesFlags.IsLinkProbeFlagSet(); + break; + case Mac::Frame::kFcfFrameData: + match = mSeriesFlags.IsMacDataFlagSet(); + break; + case Mac::Frame::kFcfFrameMacCmd: + match = mSeriesFlags.IsMacDataRequestFlagSet(); + break; + case Mac::Frame::kFcfFrameAck: + match = mSeriesFlags.IsMacAckFlagSet(); + break; + default: + break; + } + +exit: + return match; +} + LinkMetrics::LinkMetrics(Instance &aInstance) : InstanceLocator(aInstance) , mLinkMetricsReportCallback(nullptr) , mLinkMetricsReportCallbackContext(nullptr) + , mLinkMetricsSeriesInfoPool() { } otError LinkMetrics::LinkMetricsQuery(const Ip6::Address & aDestination, uint8_t aSeriesId, - const otLinkMetrics &aLinkMetricsFlags) + const otLinkMetrics *aLinkMetricsFlags) { otError error; LinkMetricsTypeIdFlags typeIdFlags[kMaxTypeIdFlags]; - uint8_t typeIdFlagsCount = TypeIdFlagsFromLinkMetricsFlags(typeIdFlags, aLinkMetricsFlags); + uint8_t typeIdFlagsCount = 0; + + if (aLinkMetricsFlags != nullptr) + { + typeIdFlagsCount = TypeIdFlagsFromLinkMetricsFlags(typeIdFlags, *aLinkMetricsFlags); + } + + if (aSeriesId != 0) + { + VerifyOrExit(typeIdFlagsCount == 0, error = OT_ERROR_INVALID_ARGS); + } error = SendLinkMetricsQuery(aDestination, aSeriesId, typeIdFlags, typeIdFlagsCount); +exit: return error; } -otError LinkMetrics::AppendLinkMetricsReport(Message &aMessage, const Message &aRequestMessage) +otError LinkMetrics::SendMgmtRequestForwardTrackingSeries(const Ip6::Address & aDestination, + uint8_t aSeriesId, + const otLinkMetricsSeriesFlags &aSeriesFlags, + const otLinkMetrics * aLinkMetricsFlags) { - otError error = OT_ERROR_NONE; - Tlv tlv; - uint8_t queryId; - bool hasQueryId = false; - uint8_t length = 0; - uint16_t startOffset = aMessage.GetLength(); - uint16_t offset; - uint16_t endOffset; - otLinkMetrics linkMetrics; + otError error = OT_ERROR_NONE; + uint8_t subTlvs[sizeof(Tlv) + sizeof(uint8_t) * 2 + sizeof(LinkMetricsTypeIdFlags) * kMaxTypeIdFlags]; + Tlv * forwardProbingRegistrationSubTlv = reinterpret_cast(subTlvs); + SeriesFlags *seriesFlags = reinterpret_cast(subTlvs + sizeof(Tlv) + sizeof(aSeriesId)); + uint8_t typeIdFlagsOffset = sizeof(Tlv) + sizeof(uint8_t) * 2; + uint8_t typeIdFlagsCount = 0; - memset(&linkMetrics, 0, sizeof(linkMetrics)); + // Directly transform `aLinkMetricsFlags` into LinkMetricsTypeIdFlags and put them into `subTlvs` + if (aLinkMetricsFlags != nullptr) + { + typeIdFlagsCount = TypeIdFlagsFromLinkMetricsFlags( + reinterpret_cast(subTlvs + typeIdFlagsOffset), *aLinkMetricsFlags); + } + + VerifyOrExit(aSeriesId > kQueryIdSingleProbe, error = OT_ERROR_INVALID_ARGS); + + forwardProbingRegistrationSubTlv->SetType(kForwardProbingRegistration); + forwardProbingRegistrationSubTlv->SetLength( + sizeof(uint8_t) * 2 + + sizeof(LinkMetricsTypeIdFlags) * + typeIdFlagsCount); // SeriesId + SeriesFlags + typeIdFlagsCount * LinkMetricsTypeIdFlags + + memcpy(subTlvs + sizeof(Tlv), &aSeriesId, sizeof(aSeriesId)); + + seriesFlags->Clear(); + if (aSeriesFlags.mLinkProbe) + { + seriesFlags->SetLinkProbeFlag(); + } + if (aSeriesFlags.mMacData) + { + seriesFlags->SetMacDataFlag(); + } + if (aSeriesFlags.mMacDataRequest) + { + seriesFlags->SetMacDataRequestFlag(); + } + if (aSeriesFlags.mMacAck) + { + seriesFlags->SetMacAckFlag(); + } + + error = Get().SendLinkMetricsManagementRequest(aDestination, subTlvs, + forwardProbingRegistrationSubTlv->GetSize()); + +exit: + otLogDebgMle("SendMgmtRequestForwardTrackingSeries, error:%s, Series ID:%u", otThreadErrorToString(error), + aSeriesId); + return error; +} + +otError LinkMetrics::SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t aLength) +{ + otError error = OT_ERROR_NONE; + uint8_t buf[kLinkProbeMaxLen]; + + VerifyOrExit(aLength <= LinkMetrics::kLinkProbeMaxLen && aSeriesId != kQueryIdSingleProbe && + aSeriesId != kSeriesIdAllSeries, + error = OT_ERROR_INVALID_ARGS); + + error = Get().SendLinkProbe(aDestination, aSeriesId, buf, aLength); +exit: + otLogDebgMle("SendLinkProbe, error:%s, Series ID:%u", otThreadErrorToString(error), aSeriesId); + return error; +} + +otError LinkMetrics::AppendLinkMetricsReport(Message &aMessage, const Message &aRequestMessage, Neighbor &aNeighbor) +{ + otError error = OT_ERROR_NONE; + Tlv tlv; + uint8_t queryId; + bool hasQueryId = false; + uint8_t length = 0; + uint16_t startOffset = aMessage.GetLength(); + uint16_t offset; + uint16_t endOffset; + otLinkMetricsValues linkMetricsValues; + + memset(&linkMetricsValues, 0, sizeof(linkMetricsValues)); SuccessOrExit(error = Tlv::FindTlvValueOffset(aRequestMessage, Mle::Tlv::Type::kLinkMetricsQuery, offset, endOffset)); // `endOffset` is used to store tlv length here @@ -95,43 +238,9 @@ otError LinkMetrics::AppendLinkMetricsReport(Message &aMessage, const Message &a break; case kLinkMetricsQueryOptions: - for (uint16_t index = offset + sizeof(tlv), endIndex = static_cast(offset + tlv.GetSize()); - index < endIndex; index += sizeof(LinkMetricsTypeIdFlags)) - { - LinkMetricsTypeIdFlags typeIdFlags; - - SuccessOrExit(error = aRequestMessage.Read(index, typeIdFlags)); - - switch (typeIdFlags.GetRawValue()) - { - case kTypeIdFlagPdu: - VerifyOrExit(!linkMetrics.mPduCount, error = OT_ERROR_PARSE); - linkMetrics.mPduCount = true; - break; - - case kTypeIdFlagLqi: - VerifyOrExit(!linkMetrics.mLqi, error = OT_ERROR_PARSE); - linkMetrics.mLqi = true; - break; - - case kTypeIdFlagLinkMargin: - VerifyOrExit(!linkMetrics.mLinkMargin, error = OT_ERROR_PARSE); - linkMetrics.mLinkMargin = true; - break; - - case kTypeIdFlagRssi: - VerifyOrExit(!linkMetrics.mRssi, error = OT_ERROR_PARSE); - linkMetrics.mRssi = true; - break; - - default: - if (typeIdFlags.IsExtendedFlagSet()) - { - index += sizeof(uint8_t); // Skip the additional second flags byte. - } - break; - } - } + SuccessOrExit(error = ReadTypeIdFlagsFromMessage(aRequestMessage, offset + sizeof(tlv), + static_cast(offset + tlv.GetSize()), + linkMetricsValues.mMetrics)); break; default: @@ -147,13 +256,44 @@ otError LinkMetrics::AppendLinkMetricsReport(Message &aMessage, const Message &a tlv.SetType(Mle::Tlv::kLinkMetricsReport); SuccessOrExit(error = aMessage.Append(tlv)); - if (queryId == 0) + if (queryId == kQueryIdSingleProbe) { - SuccessOrExit(error = AppendSingleProbeLinkMetricsReport(aMessage, length, linkMetrics, aRequestMessage)); + linkMetricsValues.mPduCountValue = aRequestMessage.GetPsduCount(); + linkMetricsValues.mLqiValue = aRequestMessage.GetAverageLqi(); + linkMetricsValues.mLinkMarginValue = + LinkQualityInfo::ConvertRssToLinkMargin(Get().GetNoiseFloor(), aRequestMessage.GetAverageRss()) * + 255 / 130; // Linear scale Link Margin from [0, 130] to [0, 255] + linkMetricsValues.mRssiValue = + (aRequestMessage.GetAverageRss() + 130) * 255 / 130; // Linear scale rss from [-130, 0] to [0, 255] + + SuccessOrExit(error = AppendReportSubTlvToMessage(aMessage, length, linkMetricsValues)); } else { - ExitNow(error = OT_ERROR_NOT_IMPLEMENTED); + LinkMetricsSeriesInfo *seriesInfo = aNeighbor.GetForwardTrackingSeriesInfo(queryId); + if (seriesInfo == nullptr) + { + SuccessOrExit( + error = AppendStatusSubTlvToMessage(aMessage, length, OT_LINK_METRICS_STATUS_SERIESID_NOT_RECOGNIZED)); + } + else if (seriesInfo->GetPduCount() == 0) + { + SuccessOrExit(error = AppendStatusSubTlvToMessage(aMessage, length, + OT_LINK_METRICS_STATUS_NO_MATCHING_FRAMES_RECEIVED)); + } + else + { + seriesInfo->GetLinkMetrics(linkMetricsValues.mMetrics); + linkMetricsValues.mPduCountValue = seriesInfo->GetPduCount(); + linkMetricsValues.mLqiValue = seriesInfo->GetAverageLqi(); + linkMetricsValues.mLinkMarginValue = + LinkQualityInfo::ConvertRssToLinkMargin(Get().GetNoiseFloor(), + seriesInfo->GetAverageRss()) * + 255 / 130; // Linear scale Link Margin from [0, 130] to [0, 255] + linkMetricsValues.mRssiValue = + (seriesInfo->GetAverageRss() + 130) * 255 / 130; // Linear scale RSSI from [-130, 0] to [0, 255] + SuccessOrExit(error = AppendReportSubTlvToMessage(aMessage, length, linkMetricsValues)); + } } tlv.SetLength(length); @@ -164,92 +304,239 @@ exit: return error; } +otError LinkMetrics::HandleLinkMetricsManagementRequest(const Message & aMessage, + Neighbor & aNeighbor, + otLinkMetricsStatus &aStatus) +{ + otError error = OT_ERROR_NONE; + Tlv tlv; + uint8_t seriesId; + SeriesFlags seriesFlags; + otLinkMetrics linkMetrics; + bool hasForwardProbingRegistrationTlv = false; + uint16_t offset; + uint16_t length; + uint16_t index = 0; + + SuccessOrExit(error = Tlv::FindTlvValueOffset(aMessage, Mle::Tlv::Type::kLinkMetricsManagement, offset, length)); + + while (index < length) + { + uint16_t pos = offset + index; + SuccessOrExit(aMessage.Read(pos, tlv)); + + pos += sizeof(tlv); + + switch (tlv.GetType()) + { + case kForwardProbingRegistration: + VerifyOrExit(!hasForwardProbingRegistrationTlv, error = OT_ERROR_PARSE); + VerifyOrExit(tlv.GetLength() >= sizeof(seriesId) + sizeof(seriesFlags), error = OT_ERROR_PARSE); + SuccessOrExit(aMessage.Read(pos, seriesId)); + pos += sizeof(seriesId); + SuccessOrExit(aMessage.Read(pos, seriesFlags)); + pos += sizeof(seriesFlags); + SuccessOrExit(error = ReadTypeIdFlagsFromMessage( + aMessage, pos, static_cast(offset + index + tlv.GetSize()), linkMetrics)); + hasForwardProbingRegistrationTlv = true; + break; + + default: + break; + } + + index += tlv.GetSize(); + } + + if (hasForwardProbingRegistrationTlv) + { + aStatus = ConfigureForwardTrackingSeries(seriesId, seriesFlags, linkMetrics, aNeighbor); + } + +exit: + return error; +} + +otError LinkMetrics::HandleLinkMetricsManagementResponse(const Message &aMessage, const Ip6::Address &aAddress) +{ + otError error = OT_ERROR_NONE; + Tlv tlv; + uint16_t offset; + uint16_t length; + uint16_t index = 0; + otLinkMetricsStatus status; + bool hasStatus = false; + + VerifyOrExit(mLinkMetricsMgmtResponseCallback != nullptr); + + SuccessOrExit(error = Tlv::FindTlvValueOffset(aMessage, Mle::Tlv::Type::kLinkMetricsManagement, offset, length)); + + while (index < length) + { + SuccessOrExit(aMessage.Read(offset + index, tlv)); + + switch (tlv.GetType()) + { + case kLinkMetricsStatus: + VerifyOrExit(!hasStatus, error = OT_ERROR_PARSE); + VerifyOrExit(tlv.GetLength() == sizeof(status), error = OT_ERROR_PARSE); + SuccessOrExit(aMessage.Read(offset + index + sizeof(tlv), status)); + hasStatus = true; + break; + + default: + break; + } + + index += tlv.GetSize(); + } + + VerifyOrExit(hasStatus, error = OT_ERROR_PARSE); + + mLinkMetricsMgmtResponseCallback(&aAddress, status, mLinkMetricsMgmtResponseCallbackContext); + +exit: + return error; +} + void LinkMetrics::HandleLinkMetricsReport(const Message & aMessage, uint16_t aOffset, uint16_t aLength, const Ip6::Address &aAddress) { + otError error = OT_ERROR_NONE; otLinkMetricsValues metricsValues; uint8_t metricsRawValue; uint16_t pos = aOffset; uint16_t endPos = aOffset + aLength; Tlv tlv; LinkMetricsTypeIdFlags typeIdFlags; + bool hasStatus = false; + bool hasReport = false; + otLinkMetricsStatus status; + + OT_UNUSED_VARIABLE(error); VerifyOrExit(mLinkMetricsReportCallback != nullptr); memset(&metricsValues, 0, sizeof(metricsValues)); - otLogDebgMle("Received Link Metrics Report"); - while (pos < endPos) { SuccessOrExit(aMessage.Read(pos, tlv)); VerifyOrExit(tlv.GetType() == kLinkMetricsReportSub); pos += sizeof(Tlv); - VerifyOrExit(pos + tlv.GetLength() <= endPos); - IgnoreError(aMessage.Read(pos, typeIdFlags)); + VerifyOrExit(pos + tlv.GetLength() <= endPos, error = OT_ERROR_PARSE); - if (typeIdFlags.IsExtendedFlagSet()) + switch (tlv.GetType()) { - pos += tlv.GetLength(); // Skip the whole sub-TLV if `E` flag is set - continue; - } - - pos += sizeof(LinkMetricsTypeIdFlags); - - switch (typeIdFlags.GetRawValue()) - { - case kTypeIdFlagPdu: - metricsValues.mMetrics.mPduCount = true; - IgnoreError(aMessage.Read(pos, metricsValues.mPduCountValue)); - pos += sizeof(uint32_t); - otLogDebgMle(" - PDU Counter: %d (Count/Summation)", metricsValues.mPduCountValue); + case kLinkMetricsStatus: + VerifyOrExit(!hasStatus && !hasReport, + error = OT_ERROR_DROP); // There should be either: one Status TLV or some Report-Sub TLVs + VerifyOrExit(tlv.GetLength() == sizeof(status), error = OT_ERROR_PARSE); + SuccessOrExit(aMessage.Read(pos, status)); + hasStatus = true; + pos += sizeof(status); break; - case kTypeIdFlagLqi: - metricsValues.mMetrics.mLqi = true; - IgnoreError(aMessage.Read(pos, metricsValues.mLqiValue)); - pos += sizeof(uint8_t); - otLogDebgMle(" - LQI: %d (Exponential Moving Average)", metricsValues.mLqiValue); - break; + case kLinkMetricsReportSub: + VerifyOrExit(!hasStatus, + error = OT_ERROR_DROP); // There shouldn't be any Report-Sub TLV when there's a Status TLV + VerifyOrExit(tlv.GetLength() == sizeof(typeIdFlags), error = OT_ERROR_PARSE); + SuccessOrExit(aMessage.Read(pos, typeIdFlags)); + if (typeIdFlags.IsExtendedFlagSet()) + { + pos += tlv.GetLength(); // Skip the whole sub-TLV if `E` flag is set + continue; + } + hasReport = true; + pos += sizeof(LinkMetricsTypeIdFlags); - case kTypeIdFlagLinkMargin: - metricsValues.mMetrics.mLinkMargin = true; - IgnoreError(aMessage.Read(pos, metricsRawValue)); - metricsValues.mLinkMarginValue = - metricsRawValue * 130 / 255; // Reverse operation for linear scale, map from [0, 255] to [0, 130] - pos += sizeof(uint8_t); - otLogDebgMle(" - Margin: %d (dB) (Exponential Moving Average)", metricsValues.mLinkMarginValue); - break; + switch (typeIdFlags.GetRawValue()) + { + case kTypeIdFlagPdu: + metricsValues.mMetrics.mPduCount = true; + SuccessOrExit(aMessage.Read(pos, metricsValues.mPduCountValue)); + pos += sizeof(uint32_t); + otLogDebgMle(" - PDU Counter: %d (Count/Summation)", metricsValues.mPduCountValue); + break; - case kTypeIdFlagRssi: - metricsValues.mMetrics.mRssi = true; - IgnoreError(aMessage.Read(pos, metricsRawValue)); - metricsValues.mRssiValue = - metricsRawValue * 130 / 255 - 130; // Reverse operation for linear scale, map from [0, 255] to [-130, 0] - pos += sizeof(uint8_t); - otLogDebgMle(" - RSSI: %d (dBm) (Exponential Moving Average)", metricsValues.mRssiValue); - break; + case kTypeIdFlagLqi: + metricsValues.mMetrics.mLqi = true; + SuccessOrExit(aMessage.Read(pos, metricsValues.mLqiValue)); + pos += sizeof(uint8_t); + otLogDebgMle(" - LQI: %d (Exponential Moving Average)", metricsValues.mLqiValue); + break; - default: + case kTypeIdFlagLinkMargin: + metricsValues.mMetrics.mLinkMargin = true; + SuccessOrExit(aMessage.Read(pos, metricsRawValue)); + metricsValues.mLinkMarginValue = + metricsRawValue * 130 / 255; // Reverse operation for linear scale, map from [0, 255] to [0, 130] + pos += sizeof(uint8_t); + otLogDebgMle(" - Margin: %d (dB) (Exponential Moving Average)", metricsValues.mLinkMarginValue); + break; + + case kTypeIdFlagRssi: + metricsValues.mMetrics.mRssi = true; + SuccessOrExit(aMessage.Read(pos, metricsRawValue)); + metricsValues.mRssiValue = metricsRawValue * 130 / 255 - + 130; // Reverse operation for linear scale, map from [0, 255] to [-130, 0] + pos += sizeof(uint8_t); + otLogDebgMle(" - RSSI: %d (dBm) (Exponential Moving Average)", metricsValues.mRssiValue); + break; + + default: + break; + } break; } } - mLinkMetricsReportCallback(&aAddress, &metricsValues, mLinkMetricsReportCallbackContext); + if (hasStatus) + { + mLinkMetricsReportCallback(&aAddress, nullptr, status, mLinkMetricsReportCallbackContext); + } + else if (hasReport) + { + mLinkMetricsReportCallback(&aAddress, &metricsValues, OT_LINK_METRICS_STATUS_SUCCESS, + mLinkMetricsReportCallbackContext); + } exit: + otLogDebgMle("HandleLinkMetricsReport, error:%s", otThreadErrorToString(error)); return; } +otError LinkMetrics::HandleLinkProbe(const Message &aMessage, uint8_t &aSeriesId) +{ + otError error = OT_ERROR_NONE; + uint16_t offset; + uint16_t length; + + SuccessOrExit(error = Tlv::FindTlvValueOffset(aMessage, Mle::Tlv::Type::kLinkProbe, offset, length)); + VerifyOrExit(length >= sizeof(aSeriesId), error = OT_ERROR_PARSE); + SuccessOrExit(error = aMessage.Read(offset, aSeriesId)); + VerifyOrExit(aSeriesId >= kQueryIdSingleProbe && aSeriesId <= kSeriesIdAllSeries, error = OT_ERROR_INVALID_ARGS); + +exit: + return error; +} + void LinkMetrics::SetLinkMetricsReportCallback(otLinkMetricsReportCallback aCallback, void *aCallbackContext) { mLinkMetricsReportCallback = aCallback; mLinkMetricsReportCallbackContext = aCallbackContext; } +void LinkMetrics::SetLinkMetricsMgmtResponseCallback(otLinkMetricsMgmtResponseCallback aCallback, + void * aCallbackContext) +{ + mLinkMetricsMgmtResponseCallback = aCallback; + mLinkMetricsMgmtResponseCallbackContext = aCallbackContext; +} + otError LinkMetrics::SendLinkMetricsQuery(const Ip6::Address & aDestination, uint8_t aSeriesId, const LinkMetricsTypeIdFlags *aTypeIdFlags, @@ -299,58 +586,41 @@ exit: return error; } -otError LinkMetrics::AppendSingleProbeLinkMetricsReport(Message & aMessage, - uint8_t & aLength, - const otLinkMetrics &aLinkMetrics, - const Message & aRequestMessage) +otLinkMetricsStatus LinkMetrics::ConfigureForwardTrackingSeries(uint8_t aSeriesId, + const SeriesFlags & aSeriesFlags, + const otLinkMetrics &aLinkMetrics, + Neighbor & aNeighbor) { - otError error = OT_ERROR_NONE; - LinkMetricsReportSubTlv metric; + otLinkMetricsStatus status = OT_LINK_METRICS_STATUS_SUCCESS; - aLength = 0; - - // Link Metrics Report sub-TLVs - if (aLinkMetrics.mPduCount) + VerifyOrExit(0 < aSeriesId, status = OT_LINK_METRICS_STATUS_OTHER_ERROR); + if (aSeriesFlags.GetRawValue() == 0) // Remove the series { - metric.Init(); - metric.SetMetricsTypeId(LinkMetricsTypeIdFlags(kTypeIdFlagPdu)); - metric.SetMetricsValue32(aRequestMessage.GetPsduCount()); - SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize())); - aLength += metric.GetSize(); + if (aSeriesId == kSeriesIdAllSeries) // Remove all + { + aNeighbor.RemoveAllForwardTrackingSeriesInfo(); + } + else + { + LinkMetricsSeriesInfo *seriesInfo = aNeighbor.RemoveForwardTrackingSeriesInfo(aSeriesId); + VerifyOrExit(seriesInfo != nullptr, status = OT_LINK_METRICS_STATUS_SERIESID_NOT_RECOGNIZED); + mLinkMetricsSeriesInfoPool.Free(*seriesInfo); + } } - - if (aLinkMetrics.mLqi) + else // Add a new series { - metric.Init(); - metric.SetMetricsTypeId(LinkMetricsTypeIdFlags(kTypeIdFlagLqi)); - metric.SetMetricsValue8(aRequestMessage.GetAverageLqi()); // IEEE 802.15.4 LQI is in scale 0-255 - SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize())); - aLength += metric.GetSize(); - } + LinkMetricsSeriesInfo *seriesInfo = aNeighbor.GetForwardTrackingSeriesInfo(aSeriesId); + VerifyOrExit(seriesInfo == nullptr, status = OT_LINK_METRICS_STATUS_SERIESID_ALREADY_REGISTERED); + seriesInfo = mLinkMetricsSeriesInfoPool.Allocate(); + VerifyOrExit(seriesInfo != nullptr, status = OT_LINK_METRICS_STATUS_CANNOT_SUPPORT_NEW_SERIES); - if (aLinkMetrics.mLinkMargin) - { - metric.Init(); - metric.SetMetricsTypeId(LinkMetricsTypeIdFlags(kTypeIdFlagLinkMargin)); - metric.SetMetricsValue8( - LinkQualityInfo::ConvertRssToLinkMargin(Get().GetNoiseFloor(), aRequestMessage.GetAverageRss()) * - 255 / 130); // Linear scale Link Margin from [0, 130] to [0, 255] - SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize())); - aLength += metric.GetSize(); - } + seriesInfo->Init(aSeriesId, aSeriesFlags, aLinkMetrics); - if (aLinkMetrics.mRssi) - { - metric.Init(); - metric.SetMetricsTypeId(LinkMetricsTypeIdFlags(kTypeIdFlagRssi)); - metric.SetMetricsValue8((aRequestMessage.GetAverageRss() + 130) * 255 / - 130); // Linear scale rss from [-130, 0] to [0, 255] - SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize())); - aLength += metric.GetSize(); + aNeighbor.AddForwardTrackingSeriesInfo(*seriesInfo); } exit: - return error; + return status; } uint8_t LinkMetrics::TypeIdFlagsFromLinkMetricsFlags(LinkMetricsTypeIdFlags *aTypeIdFlags, @@ -381,6 +651,121 @@ uint8_t LinkMetrics::TypeIdFlagsFromLinkMetricsFlags(LinkMetricsTypeIdFlags *aTy return count; } +otError LinkMetrics::ReadTypeIdFlagsFromMessage(const Message &aMessage, + uint8_t aStartPos, + uint8_t aEndPos, + otLinkMetrics &aLinkMetrics) +{ + otError error = OT_ERROR_NONE; + + memset(&aLinkMetrics, 0, sizeof(aLinkMetrics)); + + for (uint16_t pos = aStartPos; pos < aEndPos; pos += sizeof(LinkMetricsTypeIdFlags)) + { + LinkMetricsTypeIdFlags typeIdFlags; + + SuccessOrExit(aMessage.Read(pos, typeIdFlags)); + + switch (typeIdFlags.GetRawValue()) + { + case kTypeIdFlagPdu: + VerifyOrExit(!aLinkMetrics.mPduCount, error = OT_ERROR_PARSE); + aLinkMetrics.mPduCount = true; + break; + + case kTypeIdFlagLqi: + VerifyOrExit(!aLinkMetrics.mLqi, error = OT_ERROR_PARSE); + aLinkMetrics.mLqi = true; + break; + + case kTypeIdFlagLinkMargin: + VerifyOrExit(!aLinkMetrics.mLinkMargin, error = OT_ERROR_PARSE); + aLinkMetrics.mLinkMargin = true; + break; + + case kTypeIdFlagRssi: + VerifyOrExit(!aLinkMetrics.mRssi, error = OT_ERROR_PARSE); + aLinkMetrics.mRssi = true; + break; + + default: + if (typeIdFlags.IsExtendedFlagSet()) + { + pos += sizeof(uint8_t); // Skip the additional second flags byte. + } + break; + } + } + +exit: + return error; +} + +otError LinkMetrics::AppendReportSubTlvToMessage(Message & aMessage, + uint8_t & aLength, + const otLinkMetricsValues &aValues) +{ + otError error = OT_ERROR_NONE; + LinkMetricsReportSubTlv metric; + + aLength = 0; + + // Link Metrics Report sub-TLVs + if (aValues.mMetrics.mPduCount) + { + metric.Init(); + metric.SetMetricsTypeId(LinkMetricsTypeIdFlags(kTypeIdFlagPdu)); + metric.SetMetricsValue32(aValues.mPduCountValue); + SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize())); + aLength += metric.GetSize(); + } + + if (aValues.mMetrics.mLqi) + { + metric.Init(); + metric.SetMetricsTypeId(LinkMetricsTypeIdFlags(kTypeIdFlagLqi)); + metric.SetMetricsValue8(aValues.mLqiValue); + SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize())); + aLength += metric.GetSize(); + } + + if (aValues.mMetrics.mLinkMargin) + { + metric.Init(); + metric.SetMetricsTypeId(LinkMetricsTypeIdFlags(kTypeIdFlagLinkMargin)); + metric.SetMetricsValue8(aValues.mLinkMarginValue); + SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize())); + aLength += metric.GetSize(); + } + + if (aValues.mMetrics.mRssi) + { + metric.Init(); + metric.SetMetricsTypeId(LinkMetricsTypeIdFlags(kTypeIdFlagRssi)); + metric.SetMetricsValue8(aValues.mRssiValue); + SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize())); + aLength += metric.GetSize(); + } + +exit: + return error; +} + +otError LinkMetrics::AppendStatusSubTlvToMessage(Message &aMessage, uint8_t &aLength, otLinkMetricsStatus aStatus) +{ + otError error = OT_ERROR_NONE; + Tlv statusTlv; + + statusTlv.SetType(kLinkMetricsStatus); + statusTlv.SetLength(sizeof(uint8_t)); + SuccessOrExit(error = aMessage.AppendBytes(&statusTlv, sizeof(statusTlv))); + SuccessOrExit(error = aMessage.AppendBytes(&aStatus, sizeof(aStatus))); + aLength += statusTlv.GetSize(); + +exit: + return error; +} + } // namespace ot #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE diff --git a/src/core/thread/link_metrics.hpp b/src/core/thread/link_metrics.hpp index f906f8ad3..ef0b66a82 100644 --- a/src/core/thread/link_metrics.hpp +++ b/src/core/thread/link_metrics.hpp @@ -39,12 +39,15 @@ #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE #include +#include #include "common/code_utils.hpp" #include "common/locator.hpp" #include "common/non_copyable.hpp" +#include "common/pool.hpp" #include "link_metrics_tlvs.hpp" +#include "link_quality.hpp" #include "topology.hpp" namespace ot { @@ -58,8 +61,111 @@ namespace ot { * @{ */ +/** + * This class represents one Series that is being tracked by the Subject. + * + * When an Initiator successfully configured a Forward Tracking Series, the Subject would use an instance of this class + * to track the information of the Series. The Subject has a `Pool` of `LinkMetricsSeriesInfo`. It would allocate one + * when a new Series comes, and free it when a Series finishes. + * + * This class inherits `LinkedListEntry` and each `Neighbor` has a list of `LinkMetricsSeriesInfo` so that the Subject + * could track per Series initiated by neighbors as long as it has available resources. + * + */ +class LinkMetricsSeriesInfo : public LinkedListEntry +{ + friend class LinkedList; + friend class LinkedListEntry; + +public: + ///< This represents Link Probe when filtering frames to be accounted using Series Flag. There's + ///< already `kFcfFrameData`, `kFcfFrameAck` and `kFcfFrameMacCmd`. This item is added so that we can + ///< filter a Link Probe for series in the same way as other frames. + enum + { + kSeriesTypeLinkProbe = 0, + }; + + /** + * This method initializes the object. + * + * @param[in] aSeriesId The Series ID. + * @param[in] aSeriesFlags A reference to the Series Flags which specify what types of frames are to be + * accounted. + * @param[in] aLinkMetricsFlags A reference to flags specifying what metrics to query. + * + */ + void Init(uint8_t aSeriesId, const SeriesFlags &aSeriesFlags, const otLinkMetrics &aLinkMetrics); + + /** + * This method gets the Series ID. + * + * @returns The Series ID. + * + */ + uint8_t GetSeriesId() const { return mSeriesId; } + + /** + * This method gets the PDU count. + * + * @returns The PDU count. + * + */ + uint32_t GetPduCount() const { return mPduCount; } + + /** + * This method gets the average LQI. + * + * @returns The average LQI. + * + */ + uint8_t GetAverageLqi() const { return mLqiAverager.GetAverage(); } + + /** + * This method gets the average RSS. + * + * @returns The average RSS. + * + */ + int8_t GetAverageRss() const { return mRssAverager.GetAverage(); } + + /** + * This method aggregates the Link Metrics data of a frame into this series. + * + * @param[in] aFrameType The type of the frame. + * @param[in] aLqi The LQI value. + * @param[in] aRss Ths RSS value. + * + */ + void AggregateLinkMetrics(uint8_t aFrameType, uint8_t aLqi, int8_t aRss); + + /** + * This methods gets the Link Metrics Flags. + * + * @param[out] aLinkMetrics A reference to a `LinkMetrics` object to get the values. + * + */ + void GetLinkMetrics(otLinkMetrics &aLinkMetrics) const; + +private: + LinkMetricsSeriesInfo *mNext; + + uint8_t mSeriesId; + SeriesFlags mSeriesFlags; + otLinkMetrics mLinkMetrics; + RssAverager mRssAverager; + LqiAverager mLqiAverager; + uint32_t mPduCount; + + bool Matches(const uint8_t &aSeriesId) const { return mSeriesId == aSeriesId; } + + bool IsFrameTypeMatch(uint8_t aFrameType) const; +}; + class LinkMetrics : public InstanceLocator, private NonCopyable { + friend class Neighbor; + public: /** * This constructor initializes an instance of the LinkMetrics class. @@ -70,13 +176,13 @@ public: explicit LinkMetrics(Instance &aInstance); /** - * This function sends an MLE Data Request containing Link Metrics Query TLV to query Link Metrics data. + * This method sends an MLE Data Request containing Link Metrics Query TLV to query Link Metrics data. * * It could be either a Single Probe or a Forward Tracking Series. * * @param[in] aDestination A reference to the IPv6 address of the destination. * @param[in] aSeriesId The Series ID to query, 0 for single probe. - * @param[in] aLinkMetricsFlags A reference to flags specifying what metrics to query. + * @param[in] aLinkMetricsFlags A pointer to flags specifying what metrics to query. * * @retval OT_ERROR_NONE Successfully sent a Link Metrics query message. * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Data Request message. @@ -85,20 +191,81 @@ public: */ otError LinkMetricsQuery(const Ip6::Address & aDestination, uint8_t aSeriesId, - const otLinkMetrics &aLinkMetricsFlags); + const otLinkMetrics *aLinkMetricsFlags); + + /** + * This method sends an MLE Link Metrics Management Request to configure/clear a Forward Tracking Series. + * + * @param[in] aDestination A reference to the IPv6 address of the destination. + * @param[in] aSeriesId The Series ID. + * @param[in] aSeriesFlags A reference to the Series Flags which specify what types of frames are to be + * accounted. + * @param[in] aLinkMetricsFlags A pointer to flags specifying what metrics to query. + * + * @retval OT_ERROR_NONE Successfully sent a Link Metrics Management Request message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Metrics Management Request message. + * @retval OT_ERROR_INVALID_ARGS @p aSeriesId is not within the valid range. + * + */ + otError SendMgmtRequestForwardTrackingSeries(const Ip6::Address & aDestination, + uint8_t aSeriesId, + const otLinkMetricsSeriesFlags &aSeriesFlags, + const otLinkMetrics * aLinkMetricsFlags); + + /** + * This method sends an MLE Link Probe message. + * + * @param[in] aDestination A reference to the IPv6 address of the destination. + * @param[in] aSeriesId The Series ID which the Probe message targets at. + * @param[in] aLength The length of the data payload in Link Probe TLV, [0, 64]. + * + * @retval OT_ERROR_NONE Successfully sent a Link Probe message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Probe message. + * @retval OT_ERROR_INVALID_ARGS @p aSeriesId or @p aLength is not within the valid range. + * + */ + otError SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t aLength); /** * This method appends a Link Metrics Report to a message according to the Link Metrics query. * * @param[out] aMessage A reference to the message to append report. * @param[in] aRequestMessage A reference to the message of the Data Request. + * @param[in] aNeighbor A reference to the neighbor who queries the report. * * @retval OT_ERROR_NONE Successfully appended the Thread Discovery TLV. * @retval OT_ERROR_PARSE Cannot parse query sub TLV successfully. * @retval OT_ERROR_INVALID_ARGS QueryId is invalid or any Type ID is invalid. * */ - otError AppendLinkMetricsReport(Message &aMessage, const Message &aRequestMessage); + otError AppendLinkMetricsReport(Message &aMessage, const Message &aRequestMessage, Neighbor &aNeighbor); + + /** + * This method handles the received Link Metrics Management Request contained in @p aMessage and return a status. + * + * @param[in] aMessage A reference to the message that contains the Link Metrics Management Request. + * @param[in] aNeighbor A reference to the neighbor who sends the request. + * @param[out] aStatus A reference to the status which indicates the handling result. + * + * @retval OT_ERROR_NONE Successfully handled the Link Metrics Management Request. + * @retval OT_ERROR_PARSE Cannot parse sub-TLVs from @p aMessage successfully. + * + */ + otError HandleLinkMetricsManagementRequest(const Message & aMessage, + Neighbor & aNeighbor, + otLinkMetricsStatus &aStatus); + + /** + * This method handles the received Link Metrics Management Response contained in @p aMessage. + * + * @param[in] aMessage A reference to the message that contains the Link Metrics Management Response. + * @param[in] aAddress A reference to the source address of the message. + * + * @retval OT_ERROR_NONE Successfully handled the Link Metrics Management Response. + * @retval OT_ERROR_PARSE Cannot parse sub-TLVs from @p aMessage successfully. + * + */ + otError HandleLinkMetricsManagementResponse(const Message &aMessage, const Ip6::Address &aAddress); /** * This method handles the received Link Metrics report contained in @p aMessage. @@ -114,43 +281,93 @@ public: uint16_t aLength, const Ip6::Address &aAddress); + /** + * This method handles the Link Probe contained in @p aMessage. + * + * @param[in] aMessage A reference to the message that contains the Link Probe Message. + * @param[out] aSeriesId A reference to Series ID that parsed from the message. + * + * @retval OT_ERROR_NONE Successfully handled the Link Metrics Management Response. + * @retval OT_ERROR_PARSE Cannot parse sub-TLVs from @p aMessage successfully. + * + */ + otError HandleLinkProbe(const Message &aMessage, uint8_t &aSeriesId); + /** * This method registers a callback to handle Link Metrics report received. * - * @param[in] aCallback A pointer to a function that is called when link probing report is received + * @param[in] aCallback A pointer to a function that is called when a Link Metrics report is received. * @param[in] aCallbackContext A pointer to application-specific context. * */ void SetLinkMetricsReportCallback(otLinkMetricsReportCallback aCallback, void *aCallbackContext); + /** + * This method registers a callback to handle Link Metrics Management Response received. + * + * @param[in] aCallback A pointer to a function that is called when a Link Metrics Management Response is + * received. + * @param[in] aCallbackContext A pointer to application-specific context. + * + */ + void SetLinkMetricsMgmtResponseCallback(otLinkMetricsMgmtResponseCallback aCallback, void *aCallbackContext); + private: + /** + * TypeIdFlagPdu: 0x0_1_000_000 -> 0x40 ==> L bit set, type = 0 (count/summation), metric-enum = 0 (PDU rxed). + * TypeIdFlagLqi: 0x0_0_001_001 -> 0x00 ==> L bit not set, type = 1 (exp ave), metric-enum = 1 (LQI). + * TypeIdFlagLinkMargin: 0x0_0_001_010 -> 0x00 ==> L bit not set, type = 1 (exp ave), metric-enum = 2 (Link Margin). + * TypeIdFlagRssi: 0x0_0_001_011 -> 0x00 ==> L bit not set, type = 1 (exp ave), metric-enum = 3 (RSSI). + * + */ enum { kMaxTypeIdFlags = 4, - kTypeIdFlagPdu = - 0x40, ///< 0x0_1_000_000 -> 0x40 ==> L bit set, type = 0 (count/summation), metric-enum = 0 (PDU rxed) - kTypeIdFlagLqi = 0x09, ///< 0x0_0_001_001 -> 0x00 ==> L bit not set, type = 1 (exp ave), metric-enum = 1 (LQI) - kTypeIdFlagLinkMargin = - 0x0a, ///< 0x0_0_001_010 -> 0x00 ==> L bit not set, type = 1 (exp ave), metric-enum = 2 (Link Margin) - kTypeIdFlagRssi = 0x0b, ///< 0x0_0_001_011 -> 0x00 ==> L bit not set, type = 1 (exp ave), metric-enum = 3 (RSSI) + kTypeIdFlagPdu = 0x40, + kTypeIdFlagLqi = 0x09, + kTypeIdFlagLinkMargin = 0x0a, + kTypeIdFlagRssi = 0x0b, + + kMaxSeriesSupported = + OPENTHREAD_CONFIG_MLE_LINK_METRICS_MAX_SERIES_SUPPORTED, ///< Max number of LinkMetricsSeriesInfo that could + ///< be allocated by the pool. + + kQueryIdSingleProbe = 0, ///< This query ID represents Single Probe. + + kSeriesIdAllSeries = 255, ///< This series ID represents all series. + + kLinkProbeMaxLen = 64, ///< Max length of data payload in Link Probe TLV. }; - otLinkMetricsReportCallback mLinkMetricsReportCallback; - void * mLinkMetricsReportCallbackContext; + otLinkMetricsReportCallback mLinkMetricsReportCallback; + void * mLinkMetricsReportCallbackContext; + otLinkMetricsMgmtResponseCallback mLinkMetricsMgmtResponseCallback; + void * mLinkMetricsMgmtResponseCallbackContext; + + Pool mLinkMetricsSeriesInfoPool; otError SendLinkMetricsQuery(const Ip6::Address & aDestination, uint8_t aSeriesId, const LinkMetricsTypeIdFlags *aTypeIdFlags, uint8_t aTypeIdFlagsCount); - otError AppendSingleProbeLinkMetricsReport(Message & aMessage, - uint8_t & aLength, - const otLinkMetrics &aLinkMetrics, - const Message & aRequestMessage); + otLinkMetricsStatus ConfigureForwardTrackingSeries(uint8_t aSeriesId, + const SeriesFlags & aSeriesFlags, + const otLinkMetrics &aLinkMetrics, + Neighbor & aNeighbor); static uint8_t TypeIdFlagsFromLinkMetricsFlags(LinkMetricsTypeIdFlags *aTypeIdFlags, const otLinkMetrics & aLinkMetricsFlags); + + static otError ReadTypeIdFlagsFromMessage(const Message &aMessage, + uint8_t aStartPos, + uint8_t aEndPos, + otLinkMetrics &aLinkMetrics); + + static otError AppendReportSubTlvToMessage(Message &aMessage, uint8_t &aLength, const otLinkMetricsValues &aValues); + + static otError AppendStatusSubTlvToMessage(Message &aMessage, uint8_t &aLength, otLinkMetricsStatus aStatus); }; /** diff --git a/src/core/thread/link_metrics_tlvs.hpp b/src/core/thread/link_metrics_tlvs.hpp index 5e06158c2..1a2e6056b 100644 --- a/src/core/thread/link_metrics_tlvs.hpp +++ b/src/core/thread/link_metrics_tlvs.hpp @@ -41,12 +41,14 @@ #include "common/message.hpp" #include "common/tlvs.hpp" +#include "mac/mac_frame.hpp" + #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE namespace ot { /** - * Link Metrics parameters + * Link Metrics parameters. * */ enum @@ -55,18 +57,16 @@ enum }; /** - * Link Metrics Sub-TLV types + * Link Metrics Sub-TLV types. * */ -enum Type +enum Type : uint8_t { kLinkMetricsReportSub = 0, ///< Link Metrics Report Sub-TLV kLinkMetricsQueryId = 1, ///< Link Metrics Query ID Sub-TLV kLinkMetricsQueryOptions = 2, ///< Link Metrics Query Options Sub-TLV kForwardProbingRegistration = 3, ///< Forward Probing Registration Sub-TLV - kReverseProbingRegistration = 4, ///< Reverse Probing Registration Sub-TLV kLinkMetricsStatus = 5, ///< Link Metrics Status Sub-TLV - kSeriesTrackingCapabilities = 6, ///< Series Tracking Capabilities Sub-TLV kEnhancedACKConfiguration = 7, ///< Enhanced ACK Configuration Sub-TLV }; @@ -330,6 +330,140 @@ public: } OT_TOOL_PACKED_END; +/** + * This class implements Series Flags for Forward Tracking Series. + * + */ +OT_TOOL_PACKED_BEGIN +class SeriesFlags +{ +public: + /** + * Default constructor + * + */ + SeriesFlags(void) { mSeriesFlags = 0; } + + /** + * Copy constructor + * + */ + SeriesFlags(const SeriesFlags &aSeriesFlags) { mSeriesFlags = aSeriesFlags.mSeriesFlags; } + + /** + * This method clears the Link Probe flag. + * + */ + void ClearLinkProbeFlag(void) { mSeriesFlags &= ~kLinkProbeFlag; } + + /** + * This method sets the Link Probe flag. + * + */ + void SetLinkProbeFlag(void) { mSeriesFlags |= kLinkProbeFlag; } + + /** + * This method indicates whether or not the Link Probe flag is set. + * + * @retval true The Link Probe flag is set. + * @retval false The Link Probe flag is not set. + * + */ + bool IsLinkProbeFlagSet(void) const { return (mSeriesFlags & kLinkProbeFlag) != 0; } + + /** + * This method clears the MAC Data flag. + * + */ + void ClearMacDataFlag(void) { mSeriesFlags &= ~kMacDataFlag; } + + /** + * This method sets the MAC Data flag. + * + */ + void SetMacDataFlag(void) { mSeriesFlags |= kMacDataFlag; } + + /** + * This method indicates whether or not the MAC Data flag is set. + * + * @retval true The MAC Data flag is set. + * @retval false The MAC Data flag is not set. + * + */ + bool IsMacDataFlagSet(void) const { return (mSeriesFlags & kMacDataFlag) != 0; } + + /** + * This method clears the MAC Data Request flag. + * + */ + void ClearMacDataRequestFlag(void) { mSeriesFlags &= ~kMacDataRequestFlag; } + + /** + * This method sets the MAC Data Request flag. + * + */ + void SetMacDataRequestFlag(void) { mSeriesFlags |= kMacDataRequestFlag; } + + /** + * This method indicates whether or not the MAC Data Request flag is set. + * + * @retval true The MAC Data Request flag is set. + * @retval false The MAC Data Request flag is not set. + * + */ + bool IsMacDataRequestFlagSet(void) const { return (mSeriesFlags & kMacDataRequestFlag) != 0; } + + /** + * This method clears the Mac Ack flag. + * + */ + void ClearMacAckFlag(void) { mSeriesFlags &= ~kMacAckFlag; } + + /** + * This method sets the Mac Ack flag. + * + */ + void SetMacAckFlag(void) { mSeriesFlags |= kMacAckFlag; } + + /** + * This method indicates whether or not the Mac Ack flag is set. + * + * @retval true The Mac Ack flag is set. + * @retval false The Mac Ack flag is not set. + * + */ + bool IsMacAckFlagSet(void) const { return (mSeriesFlags & kMacAckFlag) != 0; } + + /** + * This method returns the raw value of mSeriesFlags. + * + */ + uint8_t GetRawValue(void) const { return mSeriesFlags; } + + /** + * This method clears the all the flags. + * + */ + void Clear(void) { mSeriesFlags = 0; } + + /** + * This method overloads the assignment operator. + * + */ + void operator=(const SeriesFlags &aSeriesFlags) { mSeriesFlags = aSeriesFlags.mSeriesFlags; } + +private: + enum + { + kLinkProbeFlag = 1 << 0, + kMacDataFlag = 1 << 1, + kMacDataRequestFlag = 1 << 2, + kMacAckFlag = 1 << 3, + }; + + uint8_t mSeriesFlags; +} OT_TOOL_PACKED_END; + } // namespace ot #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 7c9932c96..00a2aeeec 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -51,6 +51,7 @@ #include "net/udp6.hpp" #include "thread/address_resolver.hpp" #include "thread/key_manager.hpp" +#include "thread/link_metrics.hpp" #include "thread/mle_router.hpp" #include "thread/thread_netif.hpp" #include "thread/time_sync_service.hpp" @@ -2420,6 +2421,53 @@ exit: return error; } +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +otError Mle::SendLinkMetricsManagementResponse(const Ip6::Address &aDestination, otLinkMetricsStatus aStatus) +{ + otError error = OT_ERROR_NONE; + Message *message; + Tlv tlv; + ot::Tlv statusSubTlv; + + VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS); + SuccessOrExit(error = AppendHeader(*message, kCommandLinkMetricsManagementResponse)); + + tlv.SetType(Tlv::kLinkMetricsManagement); + statusSubTlv.SetType(kLinkMetricsStatus); + statusSubTlv.SetLength(sizeof(aStatus)); + tlv.SetLength(statusSubTlv.GetSize()); + + SuccessOrExit(error = message->Append(tlv)); + SuccessOrExit(error = message->Append(statusSubTlv)); + SuccessOrExit(error = message->Append(aStatus)); + + SuccessOrExit(error = SendMessage(*message, aDestination)); +exit: + return error; +} + +otError Mle::SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t *aBuf, uint8_t aLength) +{ + otError error = OT_ERROR_NONE; + Message *message; + Tlv tlv; + + VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS); + SuccessOrExit(error = AppendHeader(*message, kCommandLinkProbe)); + + tlv.SetType(Tlv::kLinkProbe); + tlv.SetLength(sizeof(aSeriesId) + aLength); + + SuccessOrExit(error = message->Append(tlv)); + SuccessOrExit(error = message->Append(aSeriesId)); + SuccessOrExit(error = message->AppendBytes(aBuf, aLength)); + + SuccessOrExit(error = SendMessage(*message, aDestination)); +exit: + return error; +} +#endif + otError Mle::SendMessage(Message &aMessage, const Ip6::Address &aDestination) { otError error = OT_ERROR_NONE; @@ -2716,6 +2764,20 @@ void Mle::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageIn #endif #endif // OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + case kCommandLinkMetricsManagementRequest: + HandleLinkMetricsManagementRequest(aMessage, aMessageInfo, neighbor); + break; + + case kCommandLinkMetricsManagementResponse: + HandleLinkMetricsManagementResponse(aMessage, aMessageInfo, neighbor); + break; + + case kCommandLinkProbe: + HandleLinkProbe(aMessage, aMessageInfo, neighbor); + break; +#endif + default: ExitNow(error = OT_ERROR_DROP); } @@ -3689,6 +3751,57 @@ exit: LogProcessError(kTypeAnnounce, error); } +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +void Mle::HandleLinkMetricsManagementRequest(const Message & aMessage, + const Ip6::MessageInfo &aMessageInfo, + Neighbor * aNeighbor) +{ + otError error = OT_ERROR_NONE; + otLinkMetricsStatus status; + + Log(kMessageReceive, kTypeLinkMetricsManagementRequest, aMessageInfo.GetPeerAddr()); + + VerifyOrExit(aNeighbor != nullptr, error = OT_ERROR_INVALID_STATE); + + SuccessOrExit(error = Get().HandleLinkMetricsManagementRequest(aMessage, *aNeighbor, status)); + error = SendLinkMetricsManagementResponse(aMessageInfo.GetPeerAddr(), status); + +exit: + LogProcessError(kTypeLinkMetricsManagementRequest, error); +} + +void Mle::HandleLinkMetricsManagementResponse(const Message & aMessage, + const Ip6::MessageInfo &aMessageInfo, + Neighbor * aNeighbor) +{ + otError error = OT_ERROR_NONE; + + Log(kMessageReceive, kTypeLinkMetricsManagementResponse, aMessageInfo.GetPeerAddr()); + + VerifyOrExit(aNeighbor != nullptr, error = OT_ERROR_INVALID_STATE); + + error = Get().HandleLinkMetricsManagementResponse(aMessage, aMessageInfo.GetPeerAddr()); + +exit: + LogProcessError(kTypeLinkMetricsManagementResponse, error); +} + +void Mle::HandleLinkProbe(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Neighbor *aNeighbor) +{ + otError error = OT_ERROR_NONE; + uint8_t seriesId; + + Log(kMessageReceive, kTypeLinkProbe, aMessageInfo.GetPeerAddr()); + + SuccessOrExit(error = Get().HandleLinkProbe(aMessage, seriesId)); + aNeighbor->AggregateLinkMetrics(seriesId, LinkMetricsSeriesInfo::kSeriesTypeLinkProbe, aMessage.GetAverageLqi(), + aMessage.GetAverageRss()); + +exit: + LogProcessError(kTypeLinkProbe, error); +} +#endif + void Mle::ProcessAnnounce(void) { uint8_t newChannel = mAlternateChannel; @@ -4046,6 +4159,17 @@ const char *Mle::MessageTypeToString(MessageType aType) break; #endif #endif // OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + case kTypeLinkMetricsManagementRequest: + str = "Link Metrics Management Request"; + break; + case kTypeLinkMetricsManagementResponse: + str = "Link Metrics Management Response"; + break; + case kTypeLinkProbe: + str = "Link Probe"; + break; +#endif } return str; @@ -4221,6 +4345,32 @@ const char *Mle::ReattachStateToString(ReattachState aState) // LCOV_EXCL_STOP +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +otError Mle::SendLinkMetricsManagementRequest(const Ip6::Address &aDestination, + const uint8_t * aSubTlvs, + uint8_t aLength) +{ + otError error = OT_ERROR_NONE; + Message *message; + Tlv tlv; + + VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS); + SuccessOrExit(error = AppendHeader(*message, kCommandLinkMetricsManagementRequest)); + + // Link Metrics Management TLV + tlv.SetType(Tlv::kLinkMetricsManagement); + tlv.SetLength(aLength); + + SuccessOrExit(error = message->AppendBytes(&tlv, sizeof(tlv))); + SuccessOrExit(error = message->AppendBytes(aSubTlvs, aLength)); + + SuccessOrExit(error = SendMessage(*message, aDestination)); + +exit: + return error; +} +#endif + void Mle::RegisterParentResponseStatsCallback(otThreadParentResponseCallback aCallback, void *aContext) { mParentResponseCb = aCallback; diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index 508633b9a..20f1a841d 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -45,6 +45,7 @@ #include "meshcop/meshcop.hpp" #include "net/udp6.hpp" #include "thread/link_metrics.hpp" +#include "thread/link_metrics_tlvs.hpp" #include "thread/mle_tlvs.hpp" #include "thread/mle_types.hpp" #include "thread/neighbor_table.hpp" @@ -719,25 +720,28 @@ protected: */ enum Command : uint8_t { - kCommandLinkRequest = 0, ///< Link Request - kCommandLinkAccept = 1, ///< Link Accept - kCommandLinkAcceptAndRequest = 2, ///< Link Accept and Reject - kCommandLinkReject = 3, ///< Link Reject - kCommandAdvertisement = 4, ///< Advertisement - kCommandUpdate = 5, ///< Update - kCommandUpdateRequest = 6, ///< Update Request - kCommandDataRequest = 7, ///< Data Request - kCommandDataResponse = 8, ///< Data Response - kCommandParentRequest = 9, ///< Parent Request - kCommandParentResponse = 10, ///< Parent Response - kCommandChildIdRequest = 11, ///< Child ID Request - kCommandChildIdResponse = 12, ///< Child ID Response - kCommandChildUpdateRequest = 13, ///< Child Update Request - kCommandChildUpdateResponse = 14, ///< Child Update Response - kCommandAnnounce = 15, ///< Announce - kCommandDiscoveryRequest = 16, ///< Discovery Request - kCommandDiscoveryResponse = 17, ///< Discovery Response - kCommandTimeSync = 99, ///< Time Sync (applicable when OPENTHREAD_CONFIG_TIME_SYNC_ENABLE enabled) + kCommandLinkRequest = 0, ///< Link Request + kCommandLinkAccept = 1, ///< Link Accept + kCommandLinkAcceptAndRequest = 2, ///< Link Accept and Reject + kCommandLinkReject = 3, ///< Link Reject + kCommandAdvertisement = 4, ///< Advertisement + kCommandUpdate = 5, ///< Update + kCommandUpdateRequest = 6, ///< Update Request + kCommandDataRequest = 7, ///< Data Request + kCommandDataResponse = 8, ///< Data Response + kCommandParentRequest = 9, ///< Parent Request + kCommandParentResponse = 10, ///< Parent Response + kCommandChildIdRequest = 11, ///< Child ID Request + kCommandChildIdResponse = 12, ///< Child ID Response + kCommandChildUpdateRequest = 13, ///< Child Update Request + kCommandChildUpdateResponse = 14, ///< Child Update Response + kCommandAnnounce = 15, ///< Announce + kCommandDiscoveryRequest = 16, ///< Discovery Request + kCommandDiscoveryResponse = 17, ///< Discovery Response + kCommandLinkMetricsManagementRequest = 18, ///< Link Metrics Management Request + kCommandLinkMetricsManagementResponse = 19, ///< Link Metrics Management Response + kCommandLinkProbe = 20, ///< Link Probe + kCommandTimeSync = 99, ///< Time Sync (applicable when OPENTHREAD_CONFIG_TIME_SYNC_ENABLE enabled) }; /** @@ -833,6 +837,11 @@ protected: #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE kTypeTimeSync, #endif +#endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + kTypeLinkMetricsManagementRequest, + kTypeLinkMetricsManagementResponse, + kTypeLinkProbe, #endif }; @@ -1485,6 +1494,39 @@ protected: static const char *ReattachStateToString(ReattachState aState); #endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + /** + * This method sends a Link Metrics Management Request message. + * + * @param[in] aDestination A reference to the IPv6 address of the destination. + * @param[in] aSubTlvs A pointer to the buffer of the sub-TLVs in the message. + * @param[in] aLength The overall length of @p aSubTlvs. + * + * @retval OT_ERROR_NONE Successfully sent a Link Metrics Management Request. + * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Metrics Management Request message. + * + */ + otError SendLinkMetricsManagementRequest(const Ip6::Address &aDestination, + const uint8_t * aSubTlvs, + uint8_t aLength); + + /** + * This method sends an MLE Link Probe message. + * + * @param[in] aDestination A reference to the IPv6 address of the destination. + * @param[in] aSeriesId The Series ID [1, 254] which the Probe message targets at. + * @param[in] aBuf A pointer to the data payload. + * @param[in] aLength The length of the data payload in Link Probe TLV, [0, 64]. + * + * @retval OT_ERROR_NONE Successfully sent a Link Metrics Management Request. + * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Metrics Management Request message. + * @retval OT_ERROR_INVALID_ARGS Series ID is not a valid value, not within range [1, 254]. + * + */ + otError SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t *aBuf, uint8_t aLength); + +#endif + Ip6::NetifUnicastAddress mLeaderAloc; ///< Leader anycast locator LeaderData mLeaderData; ///< Last received Leader Data TLV. @@ -1666,6 +1708,15 @@ private: void HandleDataResponse(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, const Neighbor *aNeighbor); void HandleParentResponse(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint32_t aKeySequence); void HandleAnnounce(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo); +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + void HandleLinkMetricsManagementRequest(const Message & aMessage, + const Ip6::MessageInfo &aMessageInfo, + Neighbor * aNeighbor); + void HandleLinkMetricsManagementResponse(const Message & aMessage, + const Ip6::MessageInfo &aMessageInfo, + Neighbor * aNeighbor); + void HandleLinkProbe(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Neighbor *aNeighbor); +#endif otError HandleLeaderData(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo); void ProcessAnnounce(void); bool HasUnregisteredAddress(void); @@ -1676,6 +1727,9 @@ private: otError SendOrphanAnnounce(void); bool PrepareAnnounceState(void); void SendAnnounce(uint8_t aChannel, bool aOrphanAnnounce, const Ip6::Address &aDestination); +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + otError SendLinkMetricsManagementResponse(const Ip6::Address &aDestination, otLinkMetricsStatus aStatus); +#endif uint32_t Reattach(void); bool IsBetterParent(uint16_t aRloc16, diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp index d6189190d..3e03be1a1 100644 --- a/src/core/thread/mle_router.cpp +++ b/src/core/thread/mle_router.cpp @@ -3256,7 +3256,9 @@ void MleRouter::SendDataResponse(const Ip6::Address &aDestination, #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE case Tlv::kLinkMetricsReport: OT_ASSERT(aRequestMessage != nullptr); - SuccessOrExit(error = Get().AppendLinkMetricsReport(*message, *aRequestMessage)); + neighbor = mNeighborTable.FindNeighbor(aDestination); + VerifyOrExit(neighbor != nullptr, error = OT_ERROR_INVALID_STATE); + SuccessOrExit(error = Get().AppendLinkMetricsReport(*message, *aRequestMessage, *neighbor)); break; #endif } @@ -3367,6 +3369,9 @@ void MleRouter::RemoveNeighbor(Neighbor &aNeighbor) aNeighbor.GetLinkInfo().Clear(); aNeighbor.SetState(Neighbor::kStateInvalid); +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + aNeighbor.RemoveAllForwardTrackingSeriesInfo(); +#endif exit: return; diff --git a/src/core/thread/mle_tlvs.hpp b/src/core/thread/mle_tlvs.hpp index 30f8d3565..e5e0d1a24 100644 --- a/src/core/thread/mle_tlvs.hpp +++ b/src/core/thread/mle_tlvs.hpp @@ -75,37 +75,39 @@ public: */ enum Type { - kSourceAddress = 0, ///< Source Address TLV - kMode = 1, ///< Mode TLV - kTimeout = 2, ///< Timeout TLV - kChallenge = 3, ///< Challenge TLV - kResponse = 4, ///< Response TLV - kLinkFrameCounter = 5, ///< Link-Layer Frame Counter TLV - kLinkQuality = 6, ///< Link Quality TLV - kNetworkParameter = 7, ///< Network Parameter TLV - kMleFrameCounter = 8, ///< MLE Frame Counter TLV - kRoute = 9, ///< Route64 TLV - kAddress16 = 10, ///< Address16 TLV - kLeaderData = 11, ///< Leader Data TLV - kNetworkData = 12, ///< Network Data TLV - kTlvRequest = 13, ///< TLV Request TLV - kScanMask = 14, ///< Scan Mask TLV - kConnectivity = 15, ///< Connectivity TLV - kLinkMargin = 16, ///< Link Margin TLV - kStatus = 17, ///< Status TLV - kVersion = 18, ///< Version TLV - kAddressRegistration = 19, ///< Address Registration TLV - kChannel = 20, ///< Channel TLV - kPanId = 21, ///< PAN ID TLV - kActiveTimestamp = 22, ///< Active Timestamp TLV - kPendingTimestamp = 23, ///< Pending Timestamp TLV - kActiveDataset = 24, ///< Active Operational Dataset TLV - kPendingDataset = 25, ///< Pending Operational Dataset TLV - kDiscovery = 26, ///< Thread Discovery TLV - kCslChannel = 80, ///< CSL Channel TLV - kCslTimeout = 85, ///< CSL Timeout TLV - kLinkMetricsQuery = 87, ///< Link Metrics Query TLV - kLinkMetricsReport = 89, ///< Link Metrics Report TLV + kSourceAddress = 0, ///< Source Address TLV + kMode = 1, ///< Mode TLV + kTimeout = 2, ///< Timeout TLV + kChallenge = 3, ///< Challenge TLV + kResponse = 4, ///< Response TLV + kLinkFrameCounter = 5, ///< Link-Layer Frame Counter TLV + kLinkQuality = 6, ///< Link Quality TLV + kNetworkParameter = 7, ///< Network Parameter TLV + kMleFrameCounter = 8, ///< MLE Frame Counter TLV + kRoute = 9, ///< Route64 TLV + kAddress16 = 10, ///< Address16 TLV + kLeaderData = 11, ///< Leader Data TLV + kNetworkData = 12, ///< Network Data TLV + kTlvRequest = 13, ///< TLV Request TLV + kScanMask = 14, ///< Scan Mask TLV + kConnectivity = 15, ///< Connectivity TLV + kLinkMargin = 16, ///< Link Margin TLV + kStatus = 17, ///< Status TLV + kVersion = 18, ///< Version TLV + kAddressRegistration = 19, ///< Address Registration TLV + kChannel = 20, ///< Channel TLV + kPanId = 21, ///< PAN ID TLV + kActiveTimestamp = 22, ///< Active Timestamp TLV + kPendingTimestamp = 23, ///< Pending Timestamp TLV + kActiveDataset = 24, ///< Active Operational Dataset TLV + kPendingDataset = 25, ///< Pending Operational Dataset TLV + kDiscovery = 26, ///< Thread Discovery TLV + kCslChannel = 80, ///< CSL Channel TLV + kCslTimeout = 85, ///< CSL Timeout TLV + kLinkMetricsQuery = 87, ///< Link Metrics Query TLV + kLinkMetricsManagement = 88, ///< Link Metrics Management TLV + kLinkMetricsReport = 89, ///< Link Metrics Report TLV + kLinkProbe = 90, ///< Link Probe TLV /** * Applicable/Required only when time synchronization service diff --git a/src/core/thread/topology.cpp b/src/core/thread/topology.cpp index 18d351226..058955d3c 100644 --- a/src/core/thread/topology.cpp +++ b/src/core/thread/topology.cpp @@ -159,6 +159,45 @@ void Neighbor::GenerateChallenge(void) Random::Crypto::FillBuffer(mValidPending.mPending.mChallenge, sizeof(mValidPending.mPending.mChallenge))); } +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +void Neighbor::AggregateLinkMetrics(uint8_t aSeriesId, uint8_t aFrameType, uint8_t aLqi, int8_t aRss) +{ + for (LinkMetricsSeriesInfo *entry = mLinkMetricsSeriesInfoList.GetHead(); entry != nullptr; + entry = entry->GetNext()) + { + if (aSeriesId == 0 || aSeriesId == entry->GetSeriesId()) + { + entry->AggregateLinkMetrics(aFrameType, aLqi, aRss); + } + } +} + +LinkMetricsSeriesInfo *Neighbor::GetForwardTrackingSeriesInfo(const uint8_t &aSeriesId) +{ + LinkMetricsSeriesInfo *prev; + return mLinkMetricsSeriesInfoList.FindMatching(aSeriesId, prev); +} + +void Neighbor::AddForwardTrackingSeriesInfo(LinkMetricsSeriesInfo &aLinkMetricsSeriesInfo) +{ + mLinkMetricsSeriesInfoList.Push(aLinkMetricsSeriesInfo); +} + +LinkMetricsSeriesInfo *Neighbor::RemoveForwardTrackingSeriesInfo(const uint8_t &aSeriesId) +{ + return mLinkMetricsSeriesInfoList.RemoveMatching(aSeriesId); +} + +void Neighbor::RemoveAllForwardTrackingSeriesInfo(void) +{ + while (!mLinkMetricsSeriesInfoList.IsEmpty()) + { + LinkMetricsSeriesInfo *seriesInfo = mLinkMetricsSeriesInfoList.Pop(); + Get().mLinkMetricsSeriesInfoPool.Free(*seriesInfo); + } +} +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + void Child::Info::SetFrom(const Child &aChild) { Clear(); diff --git a/src/core/thread/topology.hpp b/src/core/thread/topology.hpp index a3c28b51a..3630ff73b 100644 --- a/src/core/thread/topology.hpp +++ b/src/core/thread/topology.hpp @@ -39,6 +39,7 @@ #include #include "common/clearable.hpp" +#include "common/linked_list.hpp" #include "common/locator.hpp" #include "common/message.hpp" #include "common/random.hpp" @@ -47,12 +48,17 @@ #include "net/ip6.hpp" #include "thread/csl_tx_scheduler.hpp" #include "thread/indirect_sender.hpp" +#include "thread/link_metrics.hpp" #include "thread/link_quality.hpp" #include "thread/mle_tlvs.hpp" #include "thread/mle_types.hpp" namespace ot { +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +class LinkMetricsSeriesInfo; ///< Forward declaration for including each other with `link_metrics.hpp` +#endif + /** * This class represents a Thread neighbor. * @@ -589,6 +595,56 @@ public: void SetTimeSyncEnabled(bool aEnabled) { mTimeSyncEnabled = aEnabled; } #endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + /** + * This method aggregates the Link Metrics data into all the series that is running for this neighbor. + * + * If a series wants to account frames of @p aFrameType, it would add count by 1 and aggregrate @p aLqi and + * @p aRss into its averagers. + * + * @param[in] aSeriesId Series ID for Link Probe. Should be `0` if this method is not called by Link Probe. + * @param[in] aFrameType Type of the frame that carries Link Metrics data. + * @param[in] aLqi The LQI value. + * @param[in] aRss The Rss value. + * + */ + void AggregateLinkMetrics(uint8_t aSeriesId, uint8_t aFrameType, uint8_t aLqi, int8_t aRss); + + /** + * This method adds a new LinkMetricsSeriesInfo to the neighbor's list. + * + * @param[in] A reference to the new LinkMetricsSeriesInfo. + * + */ + void AddForwardTrackingSeriesInfo(LinkMetricsSeriesInfo &aLinkMetricsSeriesInfo); + + /** + * This method finds a specific LinkMetricsSeriesInfo by Series ID. + * + * @param[in] aSeriesId A reference to the Series ID. + * + * @returns The pointer to the LinkMetricsSeriesInfo. `nullptr` if not found. + * + */ + LinkMetricsSeriesInfo *GetForwardTrackingSeriesInfo(const uint8_t &aSeriesId); + + /** + * This method removes a specific LinkMetricsSeriesInfo by Series ID. + * + * @param[in] aSeriesId A reference to the Series ID to remove. + * + * @returns The pointer to the LinkMetricsSeriesInfo. `nullptr` if not found. + * + */ + LinkMetricsSeriesInfo *RemoveForwardTrackingSeriesInfo(const uint8_t &aSeriesId); + + /** + * This method removes all the Series and return the data structures to the Pool + * + */ + void RemoveAllForwardTrackingSeriesInfo(void); + +#endif protected: /** * This method initializes the `Neighbor` object. @@ -629,6 +685,10 @@ private: #endif uint8_t mVersion; ///< The MLE version LinkQualityInfo mLinkInfo; ///< Link quality info (contains average RSS, link margin and link quality) +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + LinkedList mLinkMetricsSeriesInfoList; ///< A list of Link Metrics Forward Tracking Series + ///< that is being tracked for this neighbor. +#endif }; /** diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py index 9812594e7..3f90b8ddd 100644 --- a/tests/scripts/thread-cert/config.py +++ b/tests/scripts/thread-cert/config.py @@ -259,7 +259,9 @@ def create_default_mle_tlvs_factories(): mle.TlvType.TIME_PARAMETER: mle.TimeParameterFactory(), mle.TlvType.THREAD_DISCOVERY: create_default_mle_tlv_thread_discovery_factory(), mle.TlvType.LINK_METRICS_QUERY: mle.LinkMetricsQueryFactory(), + mle.TlvType.LINK_METRICS_MANAGEMENT: mle.LinkMetricsManagementFactory(), mle.TlvType.LINK_METRICS_REPORT: mle.LinkMetricsReportFactory(), + mle.TlvType.LINK_PROBE: mle.LinkProbeFactory(), } diff --git a/tests/scripts/thread-cert/mle.py b/tests/scripts/thread-cert/mle.py index 16981b805..7ced8a078 100644 --- a/tests/scripts/thread-cert/mle.py +++ b/tests/scripts/thread-cert/mle.py @@ -58,6 +58,9 @@ class CommandType(IntEnum): ANNOUNCE = 15 DISCOVERY_REQUEST = 16 DISCOVERY_RESPONSE = 17 + LINK_METRICS_MANAGEMENT_REQUEST = 18 + LINK_METRICS_MANAGEMENT_RESPONSE = 19 + LINK_PROBE = 20 TIME_SYNC = 99 @@ -90,11 +93,22 @@ class TlvType(IntEnum): CSL_CHANNEL = 80 CSL_SYNCHRONIZED_TIMEOUT = 85 LINK_METRICS_QUERY = 87 + LINK_METRICS_MANAGEMENT = 88 LINK_METRICS_REPORT = 89 + LINK_PROBE = 90 TIME_REQUEST = 252 TIME_PARAMETER = 253 +class LinkMetricsSubTlvType(IntEnum): + LINK_METRICS_REPORT = 0 + LINK_METRICS_QUERY_ID = 1 + LINK_METRICS_QUERY_OPTIONS = 2 + FORWARD_PROBING_REGISTRATION = 3 + LINK_METRICS_STATUS = 5 + ENHANCED_ACK_LINK_METRICS_CONFIGURATION = 7 + + class SourceAddress(object): def __init__(self, address): @@ -1117,6 +1131,19 @@ class LinkMetricsQueryFactory: return LinkMetricsQuery() +class LinkMetricsManagement: + # TODO: Not implemented yet + + def __init__(self): + print("LinkMetricsManagement is not implemented yet.") + + +class LinkMetricsManagementFactory: + + def parse(self, data, message_info): + return LinkMetricsManagement() + + class LinkMetricsReport: # TODO: Not implemented yet @@ -1130,6 +1157,19 @@ class LinkMetricsReportFactory: return LinkMetricsReport() +class LinkProbe: + # TODO: Not implemented yet + + def __init__(self): + print("LinkProbe is not implemented yet.") + + +class LinkProbeFactory: + + def parse(self, data, message_info): + return LinkProbe() + + class MleCommand(object): def __init__(self, _type, tlvs): diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 4f9e31a43..51df79cd8 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -1920,6 +1920,22 @@ class NodeImpl: self.send_command(cmd) self._expect('Done') + def link_metrics_query_forward_tracking_series(self, dst_addr: str, series_id: int): + cmd = 'linkmetrics query %s forward %d' % (dst_addr, series_id) + self.send_command(cmd) + self._expect('Done') + + def link_metrics_mgmt_req_forward_tracking_series(self, dst_addr: str, series_id: int, series_flags: str, + metrics_flags: str): + cmd = "linkmetrics mgmt %s forward %d %s %s" % (dst_addr, series_id, series_flags, metrics_flags) + self.send_command(cmd) + self._expect('Done') + + def link_metrics_send_link_probe(self, dst_addr: str, series_id: int, length: int): + cmd = "linkmetrics probe %s %d %d" % (dst_addr, series_id, length) + self.send_command(cmd) + self._expect('Done') + def send_address_notification(self, dst: str, target: str, mliid: str): cmd = f'fake /a/an {dst} {target} {mliid}' self.send_command(cmd) diff --git a/tests/scripts/thread-cert/pktverify/consts.py b/tests/scripts/thread-cert/pktverify/consts.py index 4f51a4c94..10d9f5847 100644 --- a/tests/scripts/thread-cert/pktverify/consts.py +++ b/tests/scripts/thread-cert/pktverify/consts.py @@ -75,6 +75,9 @@ MLE_CHILD_UPDATE_RESPONSE = 14 MLE_ANNOUNCE = 15 MLE_DISCOVERY_REQUEST = 16 MLE_DISCOVERY_RESPONSE = 17 +MLE_LINK_METRICS_MANAGEMENT_REQUEST = 18 +MLE_LINK_METRICS_MANAGEMENT_RESPONSE = 19 +MLE_LINK_PROBE = 20 # COAP URIs ADDR_QRY_URI = '/a/aq' @@ -285,6 +288,22 @@ THREAD_VERSION_1_2 = 3 # ICMPv6 Types ICMPV6_TYPE_DESTINATION_UNREACHABLE = 1 +# Link Metrics +LINK_METRICS_STATUS_SUCCESS = 0 +LINK_METRICS_STATUS_CANNOT_SUPPORT_NEW_SERIES = 1 +LINK_METRICS_STATUS_SERIES_ID_ALREADY_REGISTERED = 2 +LINK_METRICS_STATUS_SERIES_ID_NOT_RECOGNIZED = 3 +LINK_METRICS_STATUS_NO_MATCHING_FRAMES_RECEIVED = 4 +LINK_METRICS_STATUS_OTHER_ERROR = 254 + +LINK_METRICS_TYPE_AVERAGE_ENUM_COUNT = 0 +LINK_METRICS_TYPE_AVERAGE_ENUM_EXPONENTIAL = 1 + +LINK_METRICS_METRIC_TYPE_ENUM_PDU_COUNT = 0 +LINK_METRICS_METRIC_TYPE_ENUM_LQI = 1 +LINK_METRICS_METRIC_TYPE_ENUM_LINK_MARGIN = 2 +LINK_METRICS_METRIC_TYPE_ENUM_RSSI = 3 + if __name__ == '__main__': from pktverify.addrs import Ipv6Addr diff --git a/tests/scripts/thread-cert/pktverify/layer_fields.py b/tests/scripts/thread-cert/pktverify/layer_fields.py index 68dbc9f54..1264884ca 100644 --- a/tests/scripts/thread-cert/pktverify/layer_fields.py +++ b/tests/scripts/thread-cert/pktverify/layer_fields.py @@ -293,6 +293,13 @@ _LAYER_FIELDS = { 'mle.tlv.addr16': _auto, 'mle.tlv.channel': _auto, 'mle.tlv.addr_reg_iid': _list(_auto), + 'mle.tlv.link_forward_series': _list(_auto), + 'mle.tlv.link_sub_tlv': _auto, + 'mle.tlv.link_status_sub_tlv': _auto, + 'mle.tlv.query_id': _auto, + 'mle.tlv.metric_type_id_flags.type': _list(_hex), + 'mle.tlv.metric_type_id_flags.metric': _list(_hex), + 'mle.tlv.metric_type_id_flags.l': _list(_hex), # IP 'ip.version': _auto, diff --git a/tests/scripts/thread-cert/v1_2_LowPower_7_2_01_ForwardTrackingSeries.py b/tests/scripts/thread-cert/v1_2_LowPower_7_2_01_ForwardTrackingSeries.py new file mode 100755 index 000000000..924495e1f --- /dev/null +++ b/tests/scripts/thread-cert/v1_2_LowPower_7_2_01_ForwardTrackingSeries.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python3 +# +# 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. +# + +import unittest + +from config import ADDRESS_TYPE +from mle import LinkMetricsSubTlvType, TlvType +from pktverify import consts +from pktverify.null_field import nullField +from pktverify.packet_verifier import PacketVerifier + +import thread_cert + +LEADER = 1 +SED_1 = 2 +SSED_1 = 3 + +SERIES_ID = 1 +SERIES_ID_2 = 2 +POLL_PERIOD = 2000 # 2s + + +class LowPower_7_2_01_ForwardTrackingSeries(thread_cert.TestCase): + TOPOLOGY = { + LEADER: { + 'version': '1.2', + 'name': 'LEADER', + 'mode': 'rdn', + 'panid': 0xface, + 'allowlist': [SED_1, SSED_1], + }, + SED_1: { + 'version': '1.2', + 'name': 'SED_1', + 'mode': '-', + 'panid': 0xface, + 'allowlist': [LEADER], + }, + SSED_1: { + 'version': '1.2', + 'name': 'SSED_1', + 'mode': '-', + 'panid': 0xface, + 'allowlist': [LEADER], + } + } + """All nodes are created with default configurations""" + + def test(self): + self.nodes[LEADER].start() + self.simulator.go(5) + self.assertEqual(self.nodes[LEADER].get_state(), 'leader') + + self.nodes[SED_1].set_pollperiod(POLL_PERIOD) + self.nodes[SED_1].start() + self.simulator.go(5) + self.assertEqual(self.nodes[SED_1].get_state(), 'child') + + self.nodes[SSED_1].set_csl_period(consts.CSL_DEFAULT_PERIOD) + self.nodes[SSED_1].start() + self.simulator.go(5) + self.assertEqual(self.nodes[SSED_1].get_state(), 'child') + + leader_addr = self.nodes[LEADER].get_ip6_address(ADDRESS_TYPE.LINK_LOCAL) + + # Step 3 - Verify connectivity by instructing each device to send an ICMPv6 Echo Request to the DUT + self.assertTrue(self.nodes[SED_1].ping(leader_addr, timeout=POLL_PERIOD)) + self.assertTrue(self.nodes[SSED_1].ping(leader_addr, timeout=2 * consts.CSL_DEFAULT_PERIOD)) + self.simulator.go(5) + + # Step 4 - SED_1 requests a Forward Tracking Series by sending a Link Metrics Management Request + # Forward Series Flags = 0x04: + # - Bit 2 = 1, MAC Data Request + # - Bits 0,1 & 3-7 = 0 + # Concatenation of Link Metric Type ID Flags = 0x00: + # - Item1: (0)(1)(000)(000) = 0x40 + # -- E = 0 + # -- L = 1 + # -- Type/Average Enum = 0 (count) + # -- Metrics Enum = 0 (count) + self.nodes[SED_1].link_metrics_mgmt_req_forward_tracking_series(leader_addr, SERIES_ID, 'r', 'p') + + self.simulator.go(5) + + # Step 6 - Configures SED_1 to send MAC Data Request every 2 seconds and wait for 30 seconds + self.simulator.go(30) + + # Step 7 - SED_1 sends an MLE Data Request to retrieve aggregated Forward Series Results + self.nodes[SED_1].link_metrics_query_forward_tracking_series(leader_addr, SERIES_ID) + + self.simulator.go(5) + + # Step 9 - SED_1 clears the Forward Tracking Series + # Forward Series Flags = 0x00: + # - Bits 0-7 = 0 + # Concatenation of Link Metric Type ID Flags = NULL: + self.nodes[SED_1].link_metrics_mgmt_req_forward_tracking_series(leader_addr, SERIES_ID, 'X', '') + + self.simulator.go(5) + + # Step 16 - SSED_1 requests a Forward Tracking Series by sending a Link Metrics Management Request + # Forward Series Flags = 0x02: + # - Bit 1 = 1, (all Link Layer Data Frames) + # - Bits 0, 2-7 = 0 + # Concatenation of Link Metric Type ID Flags = 0x0a: + # - Item1: (0)(0)(001)(010) = 0x0a + # -- E = 0 + # -- L = 0 + # -- Type/Average Enum = 1 (Exponential Moving Average) + # -- Metrics Enum = 2 (Link Margin) + self.nodes[SSED_1].link_metrics_mgmt_req_forward_tracking_series(leader_addr, SERIES_ID_2, 'd', 'm') + + self.simulator.go(5) + + # Step 18 - SSED_1 sends MAC Data packet every 1 second for 30 seconds + # Note: As the api to send MAC Data frame hasn't been implemented, we send MLE Link Probe message here + for i in range(30): + self.nodes[SSED_1].link_metrics_send_link_probe(leader_addr, SERIES_ID_2, 1) + self.simulator.go(1) + + # Step 19 - SSED_1 sends an MLE Data Request to retrieve aggregated Forward Series Results + self.nodes[SSED_1].link_metrics_query_forward_tracking_series(leader_addr, SERIES_ID_2) + + self.simulator.go(5) + + # Step 21 - SSED_1 clears the Forward Series Link Metrics + # Forward Series Flags = 0x00: + # - Bits 0-7 = 0 + # Concatenation of Link Metric Type ID Flags = NULL + self.nodes[SSED_1].link_metrics_mgmt_req_forward_tracking_series(leader_addr, SERIES_ID_2, 'X', '') + + self.simulator.go(5) + + # Step 23 - SSED_1 sends an MLE Data Request to retrieve aggregated Forward Series Results + self.nodes[SSED_1].link_metrics_query_forward_tracking_series(leader_addr, SERIES_ID_2) + + self.simulator.go(5) + + def verify(self, pv): + pkts = pv.pkts + pv.summary.show() + LEADER = pv.vars['LEADER'] + SED_1 = pv.vars['SED_1'] + SSED_1 = pv.vars['SSED_1'] + + # Step 3 - The DUT MUST send ICMPv6 Echo Responses to both SED1 & SSED1 + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(SED_1) \ + .filter_ping_reply() \ + .must_next() + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(SSED_1) \ + .filter_ping_reply() \ + .must_next() + + # Step 4 - SED_1 requests a Forward Series Link Metrics by sending a Link Metrics Management Request + # Forward Series Flags = 0x04: + # - Bit 2 = 1, MAC Data Request + # - Bits 0,1 & 3-7 = 0 + # Concatenation of Link Metric Type ID Flags = 0x40: + # - Item1: (0)(1)(000)(000) = 0x40 + # -- E = 0 + # -- L = 1 + # -- Type/Average Enum = 0 (count) + # -- Metrics Enum = 0 (count) + pkts.filter_wpan_src64(SED_1) \ + .filter_wpan_dst64(LEADER) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \ + .filter(lambda p: p.mle.tlv.link_sub_tlv == LinkMetricsSubTlvType.FORWARD_PROBING_REGISTRATION) \ + .filter(lambda p: 4 in p.mle.tlv.link_forward_series) \ + .filter(lambda p: consts.LINK_METRICS_TYPE_AVERAGE_ENUM_COUNT in p.mle.tlv.metric_type_id_flags.type) \ + .filter(lambda p: consts.LINK_METRICS_METRIC_TYPE_ENUM_PDU_COUNT in p.mle.tlv.metric_type_id_flags.metric) \ + .filter(lambda p: 1 in p.mle.tlv.metric_type_id_flags.l) \ + .must_next() + + # Step 5 - Leader responds with a Link Metrics Management Response + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(SED_1) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SUCCESS) \ + .must_next() + + # Step 8 - Leader responds with an MLE Data Response + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(SED_1) \ + .filter_mle_cmd(consts.MLE_DATA_RESPONSE) \ + .filter(lambda p: TlvType.LINK_METRICS_REPORT in p.mle.tlv.type) \ + .filter(lambda p: consts.LINK_METRICS_TYPE_AVERAGE_ENUM_COUNT in p.mle.tlv.metric_type_id_flags.type) \ + .filter(lambda p: consts.LINK_METRICS_METRIC_TYPE_ENUM_PDU_COUNT in p.mle.tlv.metric_type_id_flags.metric) \ + .must_next() + + # Step 9 - SED_1 clears the Forward Series Link Metrics + # Forward Series Flags = 0x00: + # - Bits 0-7 = 0 + # Concatenation of Link Metric Type ID Flags = NULL: + pkts.filter_wpan_src64(SED_1) \ + .filter_wpan_dst64(LEADER) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \ + .filter(lambda p: p.mle.tlv.link_sub_tlv == LinkMetricsSubTlvType.FORWARD_PROBING_REGISTRATION) \ + .filter(lambda p: 0 in p.mle.tlv.link_forward_series) \ + .filter(lambda p: p.mle.tlv.metric_type_id_flags.type is nullField) \ + .filter(lambda p: p.mle.tlv.metric_type_id_flags.metric is nullField) \ + .must_next() + + # Step 10 - Leader responds with a Link Metrics Management Response + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(SED_1) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SUCCESS) \ + .must_next() + + # Step 16 - SSED_1 requests a Forward Series Link Metrics by sending a Link Metrics Management Request + # Forward Series Flags = 0x02: + # - Bit 1 = 1, (all Link Layer Data Frames) + # - Bits 0, 2-7 = 0 + # Concatenation of Link Metric Type ID Flags = 0x0a: + # - Item1: (0)(0)(001)(010) = 0x0a + # -- E = 0 + # -- L = 0 + # -- Type/Average Enum = 1 (Exponential Moving Average) + # -- Metrics Enum = 2 (Link Margin) + pkts.filter_wpan_src64(SSED_1) \ + .filter_wpan_dst64(LEADER) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \ + .filter(lambda p: p.mle.tlv.link_sub_tlv == LinkMetricsSubTlvType.FORWARD_PROBING_REGISTRATION) \ + .filter(lambda p: 2 in p.mle.tlv.link_forward_series) \ + .filter(lambda p: consts.LINK_METRICS_TYPE_AVERAGE_ENUM_EXPONENTIAL in p.mle.tlv.metric_type_id_flags.type) \ + .filter(lambda p: consts.LINK_METRICS_METRIC_TYPE_ENUM_LINK_MARGIN in p.mle.tlv.metric_type_id_flags.metric) \ + .must_next() + + # Step 17 - Leader responds with a Link Metrics Management Response + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(SSED_1) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SUCCESS) \ + .must_next() + + # Step 19 - SSED_1 sends an MLE Data Request to retrieve aggregated Forward Series results + # - TLV Request TLV(Link Metrics Report TLV specified + # - Link Metrics Query TLV + # -- Link Metrics Query ID Sub-TLV + # --- Query ID: (ID set in step 16) + # - Verify that the Link Metrics Query Options Sub-TLV is NOT present + pkts.filter_wpan_src64(SSED_1) \ + .filter_wpan_dst64(LEADER) \ + .filter_mle_cmd(consts.MLE_DATA_REQUEST) \ + .filter(lambda p: TlvType.LINK_METRICS_QUERY in p.mle.tlv.type) \ + .filter(lambda p: p.mle.tlv.query_id == SERIES_ID_2) \ + .must_next() + + # Step 20 - Leader MUST reply to SSED_1 with an MLE Data Response with the following: + # - Link Metrics Report TLV + # -- Link Metrics Report Sub-TLV + # --- Metric Type ID Flags + # ---- Type / Average Enum = 1 (Exponential Moving Average) + # ---- Metric Enum = 2 (Link Margin) + # --- Value (1 bytes) + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(SSED_1) \ + .filter_mle_cmd(consts.MLE_DATA_RESPONSE) \ + .filter_mle_has_tlv(TlvType.LINK_METRICS_REPORT) \ + .filter(lambda p: p.mle.tlv.link_sub_tlv == LinkMetricsSubTlvType.LINK_METRICS_REPORT) \ + .filter(lambda p: consts.LINK_METRICS_TYPE_AVERAGE_ENUM_EXPONENTIAL in p.mle.tlv.metric_type_id_flags.type) \ + .filter(lambda p: consts.LINK_METRICS_METRIC_TYPE_ENUM_LINK_MARGIN in p.mle.tlv.metric_type_id_flags.metric) \ + .must_next() + + # Step 21 - SSED_1 clears the Forward Series Link Metrics + # Forward Series Flags = 0x00: + # - Bits 0-7 = 0 + # Concatenation of Link Metric Type ID Flags = NULL + pkts.filter_wpan_src64(SSED_1) \ + .filter_wpan_dst64(LEADER) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \ + .filter(lambda p: p.mle.tlv.link_sub_tlv == LinkMetricsSubTlvType.FORWARD_PROBING_REGISTRATION) \ + .filter(lambda p: 0 in p.mle.tlv.link_forward_series) \ + .filter(lambda p: p.mle.tlv.metric_type_id_flags.type is nullField) \ + .filter(lambda p: p.mle.tlv.metric_type_id_flags.metric is nullField) \ + .must_next() + + # Step 22 - Leader responds with a Link Metrics Management Response + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(SSED_1) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SUCCESS) \ + .must_next() + + # Step 23 - SSED_1 sends an MLE Data Request to retrieve aggregated Forward Series results + # - TLV Request TLV(Link Metrics Report TLV specified + # - Link Metrics Query TLV + # -- Link Metrics Query ID Sub-TLV + # --- Query ID: (ID set in step 16) + # This is a negative step + pkts.filter_wpan_src64(SSED_1) \ + .filter_wpan_dst64(LEADER) \ + .filter_mle_cmd(consts.MLE_DATA_REQUEST) \ + .filter(lambda p: TlvType.LINK_METRICS_QUERY in p.mle.tlv.type) \ + .filter(lambda p: p.mle.tlv.query_id == SERIES_ID_2) \ + .must_next() + + # Step 24 - Leader MUST reply to SSED_1 with an MLE Data Response with the following: + # - Link Metrics Report TLV + # -- Link Metrics Status Sub-TLV + # --- Status = 3 (Series ID not recognized) + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(SSED_1) \ + .filter_mle_cmd(consts.MLE_DATA_RESPONSE) \ + .filter_mle_has_tlv(TlvType.LINK_METRICS_REPORT) \ + .filter(lambda p: p.mle.tlv.link_sub_tlv == LinkMetricsSubTlvType.LINK_METRICS_STATUS) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SERIES_ID_NOT_RECOGNIZED) \ + .must_next() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/v1_2_LowPower_test_forward_tracking_series.py b/tests/scripts/thread-cert/v1_2_LowPower_test_forward_tracking_series.py new file mode 100755 index 000000000..c952083e4 --- /dev/null +++ b/tests/scripts/thread-cert/v1_2_LowPower_test_forward_tracking_series.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +# +# 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. +# + +import unittest + +from config import ADDRESS_TYPE +from mle import LinkMetricsSubTlvType, TlvType +from pktverify import consts +from pktverify.null_field import nullField +from pktverify.packet_verifier import PacketVerifier + +import thread_cert + +LEADER = 1 +CHILD = 2 + +SERIES_ID_1 = 1 +SERIES_ID_2 = 2 + + +class LowPower_test_ForwardTrackingSeries(thread_cert.TestCase): + TOPOLOGY = { + LEADER: { + 'version': '1.2', + 'name': 'LEADER', + 'mode': 'rdn', + 'panid': 0xface, + 'allowlist': [CHILD], + }, + CHILD: { + 'version': '1.2', + 'name': 'CHILD', + 'mode': 'rn', + 'panid': 0xface, + 'allowlist': [LEADER], + } + } + """All nodes are created with default configurations""" + + def test(self): + self.nodes[LEADER].start() + self.simulator.go(5) + self.assertEqual(self.nodes[LEADER].get_state(), 'leader') + + self.nodes[CHILD].start() + self.simulator.go(5) + self.assertEqual(self.nodes[CHILD].get_state(), 'child') + + leader_addr = self.nodes[LEADER].get_ip6_address(ADDRESS_TYPE.LINK_LOCAL) + + # 1. Child configures a Forward Tracking Series successfully. + # The Series tracks the count of MAC Data Request. + # Child should get a response with status 0 (SUCCESS). + self.nodes[CHILD].link_metrics_mgmt_req_forward_tracking_series(leader_addr, SERIES_ID_1, 'r', 'p') + self.simulator.go(1) + + # 2. Child configures the same Forward Tracking Series again. + # Child should get a response with status 2 (SERIES_ID_ALREADY_REGISTERED). + self.nodes[CHILD].link_metrics_mgmt_req_forward_tracking_series(leader_addr, SERIES_ID_1, 'r', 'p') + self.simulator.go(1) + + # 3. Child queries a Series that doesn't exist (using a wrong Series ID). + # Child should get a report with status 3 (SERIES_ID_NOT_RECOGNIZED). + self.nodes[CHILD].link_metrics_query_forward_tracking_series(leader_addr, SERIES_ID_2) + self.simulator.go(1) + + # 4. Child queries a Series that don't have matched frames yet. + # Child should get a report with status 4 (NO_MATCHING_FRAMES_RECEIVED). + self.nodes[CHILD].link_metrics_query_forward_tracking_series(leader_addr, SERIES_ID_1) + self.simulator.go(1) + + # 5. Child clears a Forward Tracking Series that doesn't exist. + # Child should get a response with status 3 (SERIES_ID_NOT_RECOGNIZED). + self.nodes[CHILD].link_metrics_mgmt_req_forward_tracking_series(leader_addr, SERIES_ID_2, 'X', '') + self.simulator.go(1) + + # 6. Child clears a Forward Tracking Series successfully. + # Child should get a response with status 0 (SUCCESS). + self.nodes[CHILD].link_metrics_mgmt_req_forward_tracking_series(leader_addr, SERIES_ID_1, 'X', '') + self.simulator.go(1) + + # 7. Child configures a new Forward Tracking Series successfully. + # The Series tracks the count of all MAC Data frames. + # Child should get a response with status 0 (SUCCESS). + self.nodes[CHILD].link_metrics_mgmt_req_forward_tracking_series(leader_addr, SERIES_ID_2, 'd', 'pqmr') + self.simulator.go(1) + + # 8. Child sends an MLE Link Probe message to the Subject for the newly configured Series. + self.nodes[CHILD].link_metrics_send_link_probe(leader_addr, SERIES_ID_2, 1) + + # 9. Child queries the newly configured Series successfully. + # Child should get a report with valid values. + self.nodes[CHILD].link_metrics_query_forward_tracking_series(leader_addr, SERIES_ID_2) + self.simulator.go(1) + + def verify(self, pv): + pkts = pv.pkts + pv.summary.show() + LEADER = pv.vars['LEADER'] + CHILD = pv.vars['CHILD'] + + # 1. Child should get a response with status 0 (SUCCESS). + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(CHILD) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SUCCESS) \ + .must_next() + + # 2. Child should get a response with status 2 (SERIES_ID_ALREADY_REGISTERED). + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(CHILD) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SERIES_ID_ALREADY_REGISTERED) \ + .must_next() + + # 3. Child should get a report with status 3 (SERIES_ID_NOT_RECOGNIZED). + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(CHILD) \ + .filter_mle_cmd(consts.MLE_DATA_RESPONSE) \ + .filter_mle_has_tlv(TlvType.LINK_METRICS_REPORT) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SERIES_ID_NOT_RECOGNIZED) \ + .must_next() + + # 4. Child should get a report with status 4 (NO_MATCHING_FRAMES_RECEIVED). + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(CHILD) \ + .filter_mle_cmd(consts.MLE_DATA_RESPONSE) \ + .filter_mle_has_tlv(TlvType.LINK_METRICS_REPORT) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_NO_MATCHING_FRAMES_RECEIVED) \ + .must_next() + + # 5. Child should get a response with status 3 (SERIES_ID_NOT_RECOGNIZED). + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(CHILD) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SERIES_ID_NOT_RECOGNIZED) \ + .must_next() + + # 6. Child should get a response with status 0 (SUCCESS). + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(CHILD) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SUCCESS) \ + .must_next() + + # 7. Child should get a response with status 0 (SUCCESS). + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(CHILD) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SUCCESS) \ + .must_next() + + # 8. Child sends an MLE Link Probe message to the Subject for the newly configured Series. + pkts.filter_wpan_src64(CHILD) \ + .filter_wpan_dst64(LEADER) \ + .filter_mle_cmd(consts.MLE_LINK_PROBE) \ + .must_next() + + # 9. Child should get a report with valid values. + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(CHILD) \ + .filter_mle_cmd(consts.MLE_DATA_RESPONSE) \ + .filter(lambda p: TlvType.LINK_METRICS_REPORT in p.mle.tlv.type) \ + .must_next() + + +if __name__ == '__main__': + unittest.main()