1 /*
2  *  Copyright (c) 2023, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /**
30  * @file
31  *   This file implements the CLI interpreter for Link Metrics function.
32  */
33 
34 #include "cli_link_metrics.hpp"
35 
36 #include <openthread/link_metrics.h>
37 
38 #include "cli/cli.hpp"
39 #include "cli/cli_utils.hpp"
40 #include "common/code_utils.hpp"
41 
42 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
43 
44 namespace ot {
45 namespace Cli {
46 
LinkMetrics(otInstance * aInstance,OutputImplementer & aOutputImplementer)47 LinkMetrics::LinkMetrics(otInstance *aInstance, OutputImplementer &aOutputImplementer)
48     : Utils(aInstance, aOutputImplementer)
49     , mQuerySync(false)
50     , mConfigForwardTrackingSeriesSync(false)
51     , mConfigEnhAckProbingSync(false)
52 {
53 }
54 
Process(Arg aArgs[])55 template <> otError LinkMetrics::Process<Cmd("query")>(Arg aArgs[])
56 {
57     OT_UNUSED_VARIABLE(aArgs);
58     OutputLine("The command \"linkmetrics query\" has been replaced by the command \"linkmetrics request\".");
59     return OT_ERROR_INVALID_COMMAND;
60 }
61 
Process(Arg aArgs[])62 template <> otError LinkMetrics::Process<Cmd("request")>(Arg aArgs[])
63 {
64     otError       error = OT_ERROR_NONE;
65     otIp6Address  address;
66     bool          sync = true;
67     bool          isSingle;
68     uint8_t       seriesId;
69     otLinkMetrics linkMetrics;
70 
71     if (aArgs[0] == "async")
72     {
73         sync = false;
74         aArgs++;
75     }
76 
77     SuccessOrExit(error = aArgs[0].ParseAsIp6Address(address));
78 
79     /**
80      * @cli linkmetrics request single
81      * @code
82      * linkmetrics request fe80:0:0:0:3092:f334:1455:1ad2 single qmr
83      * Received Link Metrics Report from: fe80:0:0:0:3092:f334:1455:1ad2
84      * - LQI: 76 (Exponential Moving Average)
85      * - Margin: 82 (dB) (Exponential Moving Average)
86      * - RSSI: -18 (dBm) (Exponential Moving Average)
87      * Done
88      * @endcode
89      * @cparam linkmetrics request [@ca{async}] @ca{peer-ipaddr} single [@ca{pqmr}]
90      * - `async`: Use the non-blocking mode.
91      * - `peer-ipaddr`: Peer address.
92      * - [`p`, `q`, `m`, and `r`] map to #otLinkMetrics.
93      *   - `p`: Layer 2 Number of PDUs received.
94      *   - `q`: Layer 2 LQI.
95      *   - `m`: Link Margin.
96      *   - `r`: RSSI.
97      * @par
98      * Perform a Link Metrics query (Single Probe).
99      * @sa otLinkMetricsQuery
100      */
101     if (aArgs[1] == "single")
102     {
103         isSingle = true;
104         SuccessOrExit(error = ParseLinkMetricsFlags(linkMetrics, aArgs[2]));
105     }
106     /**
107      * @cli linkmetrics request forward
108      * @code
109      * linkmetrics request fe80:0:0:0:3092:f334:1455:1ad2 forward 1
110      * Received Link Metrics Report from: fe80:0:0:0:3092:f334:1455:1ad2
111      * - PDU Counter: 2 (Count/Summation)
112      * - LQI: 76 (Exponential Moving Average)
113      * - Margin: 82 (dB) (Exponential Moving Average)
114      * - RSSI: -18 (dBm) (Exponential Moving Average)
115      * Done
116      * @endcode
117      * @cparam linkmetrics query [@ca{async}] @ca{peer-ipaddr} forward @ca{series-id}
118      * - `async`: Use the non-blocking mode.
119      * - `peer-ipaddr`: Peer address.
120      * - `series-id`: The Series ID.
121      * @par
122      * Perform a Link Metrics query (Forward Tracking Series).
123      * @sa otLinkMetricsQuery
124      */
125     else if (aArgs[1] == "forward")
126     {
127         isSingle = false;
128         SuccessOrExit(error = aArgs[2].ParseAsUint8(seriesId));
129     }
130     else
131     {
132         ExitNow(error = OT_ERROR_INVALID_ARGS);
133     }
134 
135     SuccessOrExit(error = otLinkMetricsQuery(GetInstancePtr(), &address, isSingle ? 0 : seriesId,
136                                              isSingle ? &linkMetrics : nullptr, HandleLinkMetricsReport, this));
137 
138     if (sync)
139     {
140         mQuerySync = true;
141         error      = OT_ERROR_PENDING;
142     }
143 
144 exit:
145     return error;
146 }
147 
Process(Arg aArgs[])148 template <> otError LinkMetrics::Process<Cmd("mgmt")>(Arg aArgs[])
149 {
150     OT_UNUSED_VARIABLE(aArgs);
151     OutputLine("The command \"linkmetrics mgmt\" has been replaced by the command \"linkmetrics config\".");
152     return OT_ERROR_INVALID_COMMAND;
153 }
154 
Process(Arg aArgs[])155 template <> otError LinkMetrics::Process<Cmd("config")>(Arg aArgs[])
156 {
157     otError                  error;
158     otIp6Address             address;
159     otLinkMetricsSeriesFlags seriesFlags;
160     bool                     sync  = true;
161     bool                     clear = false;
162 
163     if (aArgs[0] == "async")
164     {
165         sync = false;
166         aArgs++;
167     }
168 
169     SuccessOrExit(error = aArgs[0].ParseAsIp6Address(address));
170 
171     ClearAllBytes(seriesFlags);
172 
173     /**
174      * @cli linkmetrics config forward
175      * @code
176      * linkmetrics config fe80:0:0:0:3092:f334:1455:1ad2 forward 1 dra pqmr
177      * Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2
178      * Status: SUCCESS
179      * Done
180      * @endcode
181      * @cparam linkmetrics config [@ca{async}] @ca{peer-ipaddr} forward @ca{series-id} [@ca{ldraX}][@ca{pqmr}]
182      * - `async`: Use the non-blocking mode.
183      * - `peer-ipaddr`: Peer address.
184      * - `series-id`: The Series ID.
185      * - [`l`, `d`, `r`, and `a`] map to #otLinkMetricsSeriesFlags. `X` represents none of the
186      *   `otLinkMetricsSeriesFlags`, and stops the accounting and removes the series.
187      *   - `l`: MLE Link Probe.
188      *   - `d`: MAC Data.
189      *   - `r`: MAC Data Request.
190      *   - `a`: MAC Ack.
191      *   - `X`: Can only be used without any other flags.
192      * - [`p`, `q`, `m`, and `r`] map to #otLinkMetricsValues.
193      *   - `p`: Layer 2 Number of PDUs received.
194      *   - `q`: Layer 2 LQI.
195      *   - `m`: Link Margin.
196      *   - `r`: RSSI.
197      * @par api_copy
198      * #otLinkMetricsConfigForwardTrackingSeries
199      */
200     if (aArgs[1] == "forward")
201     {
202         uint8_t       seriesId;
203         otLinkMetrics linkMetrics;
204 
205         ClearAllBytes(linkMetrics);
206         SuccessOrExit(error = aArgs[2].ParseAsUint8(seriesId));
207         VerifyOrExit(!aArgs[3].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
208 
209         for (const char *arg = aArgs[3].GetCString(); *arg != '\0'; arg++)
210         {
211             switch (*arg)
212             {
213             case 'l':
214                 seriesFlags.mLinkProbe = true;
215                 break;
216 
217             case 'd':
218                 seriesFlags.mMacData = true;
219                 break;
220 
221             case 'r':
222                 seriesFlags.mMacDataRequest = true;
223                 break;
224 
225             case 'a':
226                 seriesFlags.mMacAck = true;
227                 break;
228 
229             case 'X':
230                 VerifyOrExit(arg == aArgs[3].GetCString() && *(arg + 1) == '\0' && aArgs[4].IsEmpty(),
231                              error = OT_ERROR_INVALID_ARGS); // Ensure the flags only contain 'X'
232                 clear = true;
233                 break;
234 
235             default:
236                 ExitNow(error = OT_ERROR_INVALID_ARGS);
237             }
238         }
239 
240         if (!clear)
241         {
242             SuccessOrExit(error = ParseLinkMetricsFlags(linkMetrics, aArgs[4]));
243             VerifyOrExit(aArgs[5].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
244         }
245 
246         SuccessOrExit(error = otLinkMetricsConfigForwardTrackingSeries(
247                           GetInstancePtr(), &address, seriesId, seriesFlags, clear ? nullptr : &linkMetrics,
248                           &LinkMetrics::HandleLinkMetricsConfigForwardTrackingSeriesMgmtResponse, this));
249 
250         if (sync)
251         {
252             mConfigForwardTrackingSeriesSync = true;
253             error                            = OT_ERROR_PENDING;
254         }
255     }
256     else if (aArgs[1] == "enhanced-ack")
257     {
258         otLinkMetricsEnhAckFlags enhAckFlags;
259         otLinkMetrics            linkMetrics;
260         otLinkMetrics           *pLinkMetrics = &linkMetrics;
261 
262         /**
263          * @cli linkmetrics config enhanced-ack clear
264          * @code
265          * linkmetrics config fe80:0:0:0:3092:f334:1455:1ad2 enhanced-ack clear
266          * Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2
267          * Status: Success
268          * Done
269          * @endcode
270          * @cparam linkmetrics config [@ca{async}] @ca{peer-ipaddr} enhanced-ack clear
271          * - `async`: Use the non-blocking mode.
272          * - `peer-ipaddr` should be the Link Local address of the neighboring device.
273          * @par
274          * Sends a Link Metrics Management Request to clear an Enhanced-ACK Based Probing.
275          * @sa otLinkMetricsConfigEnhAckProbing
276          */
277         if (aArgs[2] == "clear")
278         {
279             enhAckFlags  = OT_LINK_METRICS_ENH_ACK_CLEAR;
280             pLinkMetrics = nullptr;
281         }
282         /**
283          * @cli linkmetrics config enhanced-ack register
284          * @code
285          * linkmetrics config fe80:0:0:0:3092:f334:1455:1ad2 enhanced-ack register qm
286          * Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2
287          * Status: Success
288          * Done
289          * @endcode
290          * @code
291          * > linkmetrics config fe80:0:0:0:3092:f334:1455:1ad2 enhanced-ack register qm r
292          * Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2
293          * Status: Cannot support new series
294          * Done
295          * @endcode
296          * @cparam linkmetrics config [@ca{async}] @ca{peer-ipaddr} enhanced-ack register [@ca{qmr}][@ca{r}]
297          * - `async`: Use the non-blocking mode.
298          * - [`q`, `m`, and `r`] map to #otLinkMetricsValues. Per spec 4.11.3.4.4.6, you can
299          *   only use a maximum of two options at once, for example `q`, or `qm`.
300          *   - `q`: Layer 2 LQI.
301          *   - `m`: Link Margin.
302          *   - `r`: RSSI.
303          * @par
304          * The additional `r` is optional and only used for reference devices. When this option
305          * is specified, Type/Average Enum of each Type Id Flags is set to reserved. This is
306          * used to verify that the Probing Subject correctly handles invalid Type Id Flags, and
307          * only available when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled.
308          * @par
309          * Sends a Link Metrics Management Request to register an Enhanced-ACK Based Probing.
310          * @sa otLinkMetricsConfigEnhAckProbing
311          */
312         else if (aArgs[2] == "register")
313         {
314             enhAckFlags = OT_LINK_METRICS_ENH_ACK_REGISTER;
315             SuccessOrExit(error = ParseLinkMetricsFlags(linkMetrics, aArgs[3]));
316 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
317             if (aArgs[4] == "r")
318             {
319                 linkMetrics.mReserved = true;
320             }
321 #endif
322         }
323         else
324         {
325             ExitNow(error = OT_ERROR_INVALID_ARGS);
326         }
327 
328         SuccessOrExit(
329             error = otLinkMetricsConfigEnhAckProbing(GetInstancePtr(), &address, enhAckFlags, pLinkMetrics,
330                                                      &LinkMetrics::HandleLinkMetricsConfigEnhAckProbingMgmtResponse,
331                                                      this, &LinkMetrics::HandleLinkMetricsEnhAckProbingIe, this));
332 
333         if (sync)
334         {
335             mConfigEnhAckProbingSync = true;
336             error                    = OT_ERROR_PENDING;
337         }
338     }
339     else
340     {
341         error = OT_ERROR_INVALID_ARGS;
342     }
343 
344 exit:
345     return error;
346 }
347 
Process(Arg aArgs[])348 template <> otError LinkMetrics::Process<Cmd("probe")>(Arg aArgs[])
349 {
350     /**
351      * @cli linkmetrics probe
352      * @code
353      * linkmetrics probe fe80:0:0:0:3092:f334:1455:1ad2 1 10
354      * Done
355      * @endcode
356      * @cparam linkmetrics probe @ca{peer-ipaddr} @ca{series-id} @ca{length}
357      * - `peer-ipaddr`: Peer address.
358      * - `series-id`: The Series ID for which this Probe message targets.
359      * - `length`: The length of the Probe message. A valid range is [0, 64].
360      * @par api_copy
361      * #otLinkMetricsSendLinkProbe
362      */
363     otError error = OT_ERROR_NONE;
364 
365     otIp6Address address;
366     uint8_t      seriesId;
367     uint8_t      length;
368 
369     SuccessOrExit(error = aArgs[0].ParseAsIp6Address(address));
370     SuccessOrExit(error = aArgs[1].ParseAsUint8(seriesId));
371     SuccessOrExit(error = aArgs[2].ParseAsUint8(length));
372 
373     error = otLinkMetricsSendLinkProbe(GetInstancePtr(), &address, seriesId, length);
374 exit:
375     return error;
376 }
377 
Process(Arg aArgs[])378 otError LinkMetrics::Process(Arg aArgs[])
379 {
380 #define CmdEntry(aCommandString)                                   \
381     {                                                              \
382         aCommandString, &LinkMetrics::Process<Cmd(aCommandString)> \
383     }
384 
385     static constexpr Command kCommands[] = {
386         CmdEntry("config"), CmdEntry("mgmt"), CmdEntry("probe"), CmdEntry("query"), CmdEntry("request"),
387     };
388 
389     static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
390 
391     otError        error = OT_ERROR_INVALID_COMMAND;
392     const Command *command;
393 
394     if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
395     {
396         OutputCommandTable(kCommands);
397         ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
398     }
399 
400     command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
401     VerifyOrExit(command != nullptr);
402 
403     error = (this->*command->mHandler)(aArgs + 1);
404 
405 exit:
406     return error;
407 }
408 
ParseLinkMetricsFlags(otLinkMetrics & aLinkMetrics,const Arg & aFlags)409 otError LinkMetrics::ParseLinkMetricsFlags(otLinkMetrics &aLinkMetrics, const Arg &aFlags)
410 {
411     otError error = OT_ERROR_NONE;
412 
413     VerifyOrExit(!aFlags.IsEmpty(), error = OT_ERROR_INVALID_ARGS);
414 
415     ClearAllBytes(aLinkMetrics);
416 
417     for (const char *arg = aFlags.GetCString(); *arg != '\0'; arg++)
418     {
419         switch (*arg)
420         {
421         case 'p':
422             aLinkMetrics.mPduCount = true;
423             break;
424 
425         case 'q':
426             aLinkMetrics.mLqi = true;
427             break;
428 
429         case 'm':
430             aLinkMetrics.mLinkMargin = true;
431             break;
432 
433         case 'r':
434             aLinkMetrics.mRssi = true;
435             break;
436 
437         default:
438             ExitNow(error = OT_ERROR_INVALID_ARGS);
439         }
440     }
441 
442 exit:
443     return error;
444 }
445 
HandleLinkMetricsReport(const otIp6Address * aAddress,const otLinkMetricsValues * aMetricsValues,otLinkMetricsStatus aStatus,void * aContext)446 void LinkMetrics::HandleLinkMetricsReport(const otIp6Address        *aAddress,
447                                           const otLinkMetricsValues *aMetricsValues,
448                                           otLinkMetricsStatus        aStatus,
449                                           void                      *aContext)
450 {
451     static_cast<LinkMetrics *>(aContext)->HandleLinkMetricsReport(aAddress, aMetricsValues, aStatus);
452 }
453 
PrintLinkMetricsValue(const otLinkMetricsValues * aMetricsValues)454 void LinkMetrics::PrintLinkMetricsValue(const otLinkMetricsValues *aMetricsValues)
455 {
456     static const char kLinkMetricsTypeAverage[] = "(Exponential Moving Average)";
457 
458     if (aMetricsValues->mMetrics.mPduCount)
459     {
460         OutputLine(" - PDU Counter: %lu (Count/Summation)", ToUlong(aMetricsValues->mPduCountValue));
461     }
462 
463     if (aMetricsValues->mMetrics.mLqi)
464     {
465         OutputLine(" - LQI: %u %s", aMetricsValues->mLqiValue, kLinkMetricsTypeAverage);
466     }
467 
468     if (aMetricsValues->mMetrics.mLinkMargin)
469     {
470         OutputLine(" - Margin: %u (dB) %s", aMetricsValues->mLinkMarginValue, kLinkMetricsTypeAverage);
471     }
472 
473     if (aMetricsValues->mMetrics.mRssi)
474     {
475         OutputLine(" - RSSI: %d (dBm) %s", aMetricsValues->mRssiValue, kLinkMetricsTypeAverage);
476     }
477 }
478 
HandleLinkMetricsReport(const otIp6Address * aAddress,const otLinkMetricsValues * aMetricsValues,otLinkMetricsStatus aStatus)479 void LinkMetrics::HandleLinkMetricsReport(const otIp6Address        *aAddress,
480                                           const otLinkMetricsValues *aMetricsValues,
481                                           otLinkMetricsStatus        aStatus)
482 {
483     OutputFormat("Received Link Metrics Report from: ");
484     OutputIp6AddressLine(*aAddress);
485 
486     if (aMetricsValues != nullptr)
487     {
488         PrintLinkMetricsValue(aMetricsValues);
489     }
490     else
491     {
492         OutputLine("Link Metrics Report, status: %s", LinkMetricsStatusToStr(aStatus));
493     }
494 
495     if (mQuerySync)
496     {
497         mQuerySync = false;
498         OutputResult(OT_ERROR_NONE);
499     }
500 }
501 
HandleLinkMetricsConfigForwardTrackingSeriesMgmtResponse(const otIp6Address * aAddress,otLinkMetricsStatus aStatus,void * aContext)502 void LinkMetrics::HandleLinkMetricsConfigForwardTrackingSeriesMgmtResponse(const otIp6Address *aAddress,
503                                                                            otLinkMetricsStatus aStatus,
504                                                                            void               *aContext)
505 {
506     static_cast<LinkMetrics *>(aContext)->HandleLinkMetricsConfigForwardTrackingSeriesMgmtResponse(aAddress, aStatus);
507 }
508 
HandleLinkMetricsConfigForwardTrackingSeriesMgmtResponse(const otIp6Address * aAddress,otLinkMetricsStatus aStatus)509 void LinkMetrics::HandleLinkMetricsConfigForwardTrackingSeriesMgmtResponse(const otIp6Address *aAddress,
510                                                                            otLinkMetricsStatus aStatus)
511 {
512     HandleLinkMetricsMgmtResponse(aAddress, aStatus);
513 
514     if (mConfigForwardTrackingSeriesSync)
515     {
516         mConfigForwardTrackingSeriesSync = false;
517         OutputResult(OT_ERROR_NONE);
518     }
519 }
520 
HandleLinkMetricsConfigEnhAckProbingMgmtResponse(const otIp6Address * aAddress,otLinkMetricsStatus aStatus,void * aContext)521 void LinkMetrics::HandleLinkMetricsConfigEnhAckProbingMgmtResponse(const otIp6Address *aAddress,
522                                                                    otLinkMetricsStatus aStatus,
523                                                                    void               *aContext)
524 {
525     static_cast<LinkMetrics *>(aContext)->HandleLinkMetricsConfigEnhAckProbingMgmtResponse(aAddress, aStatus);
526 }
527 
HandleLinkMetricsConfigEnhAckProbingMgmtResponse(const otIp6Address * aAddress,otLinkMetricsStatus aStatus)528 void LinkMetrics::HandleLinkMetricsConfigEnhAckProbingMgmtResponse(const otIp6Address *aAddress,
529                                                                    otLinkMetricsStatus aStatus)
530 {
531     HandleLinkMetricsMgmtResponse(aAddress, aStatus);
532 
533     if (mConfigEnhAckProbingSync)
534     {
535         mConfigEnhAckProbingSync = false;
536         OutputResult(OT_ERROR_NONE);
537     }
538 }
539 
HandleLinkMetricsMgmtResponse(const otIp6Address * aAddress,otLinkMetricsStatus aStatus)540 void LinkMetrics::HandleLinkMetricsMgmtResponse(const otIp6Address *aAddress, otLinkMetricsStatus aStatus)
541 {
542     OutputFormat("Received Link Metrics Management Response from: ");
543     OutputIp6AddressLine(*aAddress);
544 
545     OutputLine("Status: %s", LinkMetricsStatusToStr(aStatus));
546 }
547 
HandleLinkMetricsEnhAckProbingIe(otShortAddress aShortAddress,const otExtAddress * aExtAddress,const otLinkMetricsValues * aMetricsValues,void * aContext)548 void LinkMetrics::HandleLinkMetricsEnhAckProbingIe(otShortAddress             aShortAddress,
549                                                    const otExtAddress        *aExtAddress,
550                                                    const otLinkMetricsValues *aMetricsValues,
551                                                    void                      *aContext)
552 {
553     static_cast<LinkMetrics *>(aContext)->HandleLinkMetricsEnhAckProbingIe(aShortAddress, aExtAddress, aMetricsValues);
554 }
555 
HandleLinkMetricsEnhAckProbingIe(otShortAddress aShortAddress,const otExtAddress * aExtAddress,const otLinkMetricsValues * aMetricsValues)556 void LinkMetrics::HandleLinkMetricsEnhAckProbingIe(otShortAddress             aShortAddress,
557                                                    const otExtAddress        *aExtAddress,
558                                                    const otLinkMetricsValues *aMetricsValues)
559 {
560     OutputFormat("Received Link Metrics data in Enh Ack from neighbor, short address:0x%02x , extended address:",
561                  aShortAddress);
562     OutputExtAddressLine(*aExtAddress);
563 
564     if (aMetricsValues != nullptr)
565     {
566         PrintLinkMetricsValue(aMetricsValues);
567     }
568 }
569 
LinkMetricsStatusToStr(otLinkMetricsStatus aStatus)570 const char *LinkMetrics::LinkMetricsStatusToStr(otLinkMetricsStatus aStatus)
571 {
572     static const char *const kStatusStrings[] = {
573         "Success",                      // (0) OT_LINK_METRICS_STATUS_SUCCESS
574         "Cannot support new series",    // (1) OT_LINK_METRICS_STATUS_CANNOT_SUPPORT_NEW_SERIES
575         "Series ID already registered", // (2) OT_LINK_METRICS_STATUS_SERIESID_ALREADY_REGISTERED
576         "Series ID not recognized",     // (3) OT_LINK_METRICS_STATUS_SERIESID_NOT_RECOGNIZED
577         "No matching series ID",        // (4) OT_LINK_METRICS_STATUS_NO_MATCHING_FRAMES_RECEIVED
578     };
579 
580     const char *str = "Unknown error";
581 
582     static_assert(0 == OT_LINK_METRICS_STATUS_SUCCESS, "STATUS_SUCCESS is incorrect");
583     static_assert(1 == OT_LINK_METRICS_STATUS_CANNOT_SUPPORT_NEW_SERIES, "CANNOT_SUPPORT_NEW_SERIES is incorrect");
584     static_assert(2 == OT_LINK_METRICS_STATUS_SERIESID_ALREADY_REGISTERED, "SERIESID_ALREADY_REGISTERED is incorrect");
585     static_assert(3 == OT_LINK_METRICS_STATUS_SERIESID_NOT_RECOGNIZED, "SERIESID_NOT_RECOGNIZED is incorrect");
586     static_assert(4 == OT_LINK_METRICS_STATUS_NO_MATCHING_FRAMES_RECEIVED, "NO_MATCHING_FRAMES_RECEIVED is incorrect");
587 
588     if (aStatus < OT_ARRAY_LENGTH(kStatusStrings))
589     {
590         str = kStatusStrings[aStatus];
591     }
592     else if (aStatus == OT_LINK_METRICS_STATUS_OTHER_ERROR)
593     {
594         str = "Other error";
595     }
596 
597     return str;
598 }
599 
OutputResult(otError aError)600 void LinkMetrics::OutputResult(otError aError) { Interpreter::GetInterpreter().OutputResult(aError); }
601 
602 } // namespace Cli
603 } // namespace ot
604 
605 #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
606