• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     , mLinkMetricsQueryInProgress(false)
50 {
51 }
52 
Process(Arg aArgs[])53 template <> otError LinkMetrics::Process<Cmd("query")>(Arg aArgs[])
54 {
55     otError       error = OT_ERROR_NONE;
56     otIp6Address  address;
57     bool          isSingle;
58     bool          blocking;
59     uint8_t       seriesId;
60     otLinkMetrics linkMetrics;
61 
62     SuccessOrExit(error = aArgs[0].ParseAsIp6Address(address));
63 
64     /**
65      * @cli linkmetrics query single
66      * @code
67      * linkmetrics query fe80:0:0:0:3092:f334:1455:1ad2 single qmr
68      * Done
69      * > Received Link Metrics Report from: fe80:0:0:0:3092:f334:1455:1ad2
70      * - LQI: 76 (Exponential Moving Average)
71      * - Margin: 82 (dB) (Exponential Moving Average)
72      * - RSSI: -18 (dBm) (Exponential Moving Average)
73      * @endcode
74      * @cparam linkmetrics query @ca{peer-ipaddr} single [@ca{pqmr}]
75      * - `peer-ipaddr`: Peer address.
76      * - [`p`, `q`, `m`, and `r`] map to #otLinkMetrics.
77      *   - `p`: Layer 2 Number of PDUs received.
78      *   - `q`: Layer 2 LQI.
79      *   - `m`: Link Margin.
80      *   - `r`: RSSI.
81      * @par
82      * Perform a Link Metrics query (Single Probe).
83      * @sa otLinkMetricsQuery
84      */
85     if (aArgs[1] == "single")
86     {
87         isSingle = true;
88         SuccessOrExit(error = ParseLinkMetricsFlags(linkMetrics, aArgs[2]));
89     }
90     /**
91      * @cli linkmetrics query forward
92      * @code
93      * linkmetrics query fe80:0:0:0:3092:f334:1455:1ad2 forward 1
94      * Done
95      * > Received Link Metrics Report from: fe80:0:0:0:3092:f334:1455:1ad2
96      * - PDU Counter: 2 (Count/Summation)
97      * - LQI: 76 (Exponential Moving Average)
98      * - Margin: 82 (dB) (Exponential Moving Average)
99      * - RSSI: -18 (dBm) (Exponential Moving Average)
100      * @endcode
101      * @cparam linkmetrics query @ca{peer-ipaddr} forward @ca{series-id}
102      * - `peer-ipaddr`: Peer address.
103      * - `series-id`: The Series ID.
104      * @par
105      * Perform a Link Metrics query (Forward Tracking Series).
106      * @sa otLinkMetricsQuery
107      */
108     else if (aArgs[1] == "forward")
109     {
110         isSingle = false;
111         SuccessOrExit(error = aArgs[2].ParseAsUint8(seriesId));
112     }
113     else
114     {
115         ExitNow(error = OT_ERROR_INVALID_ARGS);
116     }
117 
118     blocking = (aArgs[3] == "block");
119 
120     SuccessOrExit(error = otLinkMetricsQuery(GetInstancePtr(), &address, isSingle ? 0 : seriesId,
121                                              isSingle ? &linkMetrics : nullptr, HandleLinkMetricsReport, this));
122 
123     if (blocking)
124     {
125         mLinkMetricsQueryInProgress = true;
126         error                       = OT_ERROR_PENDING;
127     }
128 exit:
129     return error;
130 }
131 
Process(Arg aArgs[])132 template <> otError LinkMetrics::Process<Cmd("mgmt")>(Arg aArgs[])
133 {
134     otError                  error;
135     otIp6Address             address;
136     otLinkMetricsSeriesFlags seriesFlags;
137     bool                     clear = false;
138 
139     SuccessOrExit(error = aArgs[0].ParseAsIp6Address(address));
140 
141     ClearAllBytes(seriesFlags);
142 
143     /**
144      * @cli linkmetrics mgmt forward
145      * @code
146      * linkmetrics mgmt fe80:0:0:0:3092:f334:1455:1ad2 forward 1 dra pqmr
147      * Done
148      * > Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2
149      * Status: SUCCESS
150      * @endcode
151      * @cparam linkmetrics mgmt @ca{peer-ipaddr} forward @ca{series-id} [@ca{ldraX}][@ca{pqmr}]
152      * - `peer-ipaddr`: Peer address.
153      * - `series-id`: The Series ID.
154      * - [`l`, `d`, `r`, and `a`] map to #otLinkMetricsSeriesFlags. `X` represents none of the
155      *   `otLinkMetricsSeriesFlags`, and stops the accounting and removes the series.
156      *   - `l`: MLE Link Probe.
157      *   - `d`: MAC Data.
158      *   - `r`: MAC Data Request.
159      *   - `a`: MAC Ack.
160      *   - `X`: Can only be used without any other flags.
161      * - [`p`, `q`, `m`, and `r`] map to #otLinkMetricsValues.
162      *   - `p`: Layer 2 Number of PDUs received.
163      *   - `q`: Layer 2 LQI.
164      *   - `m`: Link Margin.
165      *   - `r`: RSSI.
166      * @par api_copy
167      * #otLinkMetricsConfigForwardTrackingSeries
168      */
169     if (aArgs[1] == "forward")
170     {
171         uint8_t       seriesId;
172         otLinkMetrics linkMetrics;
173 
174         ClearAllBytes(linkMetrics);
175         SuccessOrExit(error = aArgs[2].ParseAsUint8(seriesId));
176         VerifyOrExit(!aArgs[3].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
177 
178         for (const char *arg = aArgs[3].GetCString(); *arg != '\0'; arg++)
179         {
180             switch (*arg)
181             {
182             case 'l':
183                 seriesFlags.mLinkProbe = true;
184                 break;
185 
186             case 'd':
187                 seriesFlags.mMacData = true;
188                 break;
189 
190             case 'r':
191                 seriesFlags.mMacDataRequest = true;
192                 break;
193 
194             case 'a':
195                 seriesFlags.mMacAck = true;
196                 break;
197 
198             case 'X':
199                 VerifyOrExit(arg == aArgs[3].GetCString() && *(arg + 1) == '\0' && aArgs[4].IsEmpty(),
200                              error = OT_ERROR_INVALID_ARGS); // Ensure the flags only contain 'X'
201                 clear = true;
202                 break;
203 
204             default:
205                 ExitNow(error = OT_ERROR_INVALID_ARGS);
206             }
207         }
208 
209         if (!clear)
210         {
211             SuccessOrExit(error = ParseLinkMetricsFlags(linkMetrics, aArgs[4]));
212             VerifyOrExit(aArgs[5].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
213         }
214 
215         error = otLinkMetricsConfigForwardTrackingSeries(GetInstancePtr(), &address, seriesId, seriesFlags,
216                                                          clear ? nullptr : &linkMetrics,
217                                                          &LinkMetrics::HandleLinkMetricsMgmtResponse, this);
218     }
219     else if (aArgs[1] == "enhanced-ack")
220     {
221         otLinkMetricsEnhAckFlags enhAckFlags;
222         otLinkMetrics            linkMetrics;
223         otLinkMetrics           *pLinkMetrics = &linkMetrics;
224 
225         /**
226          * @cli linkmetrics mgmt enhanced-ack clear
227          * @code
228          * linkmetrics mgmt fe80:0:0:0:3092:f334:1455:1ad2 enhanced-ack clear
229          * Done
230          * > Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2
231          * Status: Success
232          * @endcode
233          * @cparam linkmetrics mgmt @ca{peer-ipaddr} enhanced-ack clear
234          * `peer-ipaddr` should be the Link Local address of the neighboring device.
235          * @par
236          * Sends a Link Metrics Management Request to clear an Enhanced-ACK Based Probing.
237          * @sa otLinkMetricsConfigEnhAckProbing
238          */
239         if (aArgs[2] == "clear")
240         {
241             enhAckFlags  = OT_LINK_METRICS_ENH_ACK_CLEAR;
242             pLinkMetrics = nullptr;
243         }
244         /**
245          * @cli linkmetrics mgmt enhanced-ack register
246          * @code
247          * linkmetrics mgmt fe80:0:0:0:3092:f334:1455:1ad2 enhanced-ack register qm
248          * Done
249          * > Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2
250          * Status: Success
251          * @endcode
252          * @code
253          * > linkmetrics mgmt fe80:0:0:0:3092:f334:1455:1ad2 enhanced-ack register qm r
254          * Done
255          * > Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2
256          * Status: Cannot support new series
257          * @endcode
258          * @cparam linkmetrics mgmt @ca{peer-ipaddr} enhanced-ack register [@ca{qmr}][@ca{r}]
259          * [`q`, `m`, and `r`] map to #otLinkMetricsValues. Per spec 4.11.3.4.4.6, you can
260          * only use a maximum of two options at once, for example `q`, or `qm`.
261          * - `q`: Layer 2 LQI.
262          * - `m`: Link Margin.
263          * - `r`: RSSI.
264          * .
265          * The additional `r` is optional and only used for reference devices. When this option
266          * is specified, Type/Average Enum of each Type Id Flags is set to reserved. This is
267          * used to verify that the Probing Subject correctly handles invalid Type Id Flags, and
268          * only available when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled.
269          * @par
270          * Sends a Link Metrics Management Request to register an Enhanced-ACK Based Probing.
271          * @sa otLinkMetricsConfigEnhAckProbing
272          */
273         else if (aArgs[2] == "register")
274         {
275             enhAckFlags = OT_LINK_METRICS_ENH_ACK_REGISTER;
276             SuccessOrExit(error = ParseLinkMetricsFlags(linkMetrics, aArgs[3]));
277 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
278             if (aArgs[4] == "r")
279             {
280                 linkMetrics.mReserved = true;
281             }
282 #endif
283         }
284         else
285         {
286             ExitNow(error = OT_ERROR_INVALID_ARGS);
287         }
288 
289         error = otLinkMetricsConfigEnhAckProbing(GetInstancePtr(), &address, enhAckFlags, pLinkMetrics,
290                                                  &LinkMetrics::HandleLinkMetricsMgmtResponse, this,
291                                                  &LinkMetrics::HandleLinkMetricsEnhAckProbingIe, this);
292     }
293     else
294     {
295         error = OT_ERROR_INVALID_ARGS;
296     }
297 
298 exit:
299     return error;
300 }
301 
Process(Arg aArgs[])302 template <> otError LinkMetrics::Process<Cmd("probe")>(Arg aArgs[])
303 {
304     /**
305      * @cli linkmetrics probe
306      * @code
307      * linkmetrics probe fe80:0:0:0:3092:f334:1455:1ad2 1 10
308      * Done
309      * @endcode
310      * @cparam linkmetrics probe @ca{peer-ipaddr} @ca{series-id} @ca{length}
311      * - `peer-ipaddr`: Peer address.
312      * - `series-id`: The Series ID for which this Probe message targets.
313      * - `length`: The length of the Probe message. A valid range is [0, 64].
314      * @par api_copy
315      * #otLinkMetricsSendLinkProbe
316      */
317     otError error = OT_ERROR_NONE;
318 
319     otIp6Address address;
320     uint8_t      seriesId;
321     uint8_t      length;
322 
323     SuccessOrExit(error = aArgs[0].ParseAsIp6Address(address));
324     SuccessOrExit(error = aArgs[1].ParseAsUint8(seriesId));
325     SuccessOrExit(error = aArgs[2].ParseAsUint8(length));
326 
327     error = otLinkMetricsSendLinkProbe(GetInstancePtr(), &address, seriesId, length);
328 exit:
329     return error;
330 }
331 
Process(Arg aArgs[])332 otError LinkMetrics::Process(Arg aArgs[])
333 {
334 #define CmdEntry(aCommandString)                                   \
335     {                                                              \
336         aCommandString, &LinkMetrics::Process<Cmd(aCommandString)> \
337     }
338 
339     static constexpr Command kCommands[] = {
340         CmdEntry("mgmt"),
341         CmdEntry("probe"),
342         CmdEntry("query"),
343     };
344 
345     static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
346 
347     otError        error = OT_ERROR_INVALID_COMMAND;
348     const Command *command;
349 
350     if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
351     {
352         OutputCommandTable(kCommands);
353         ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
354     }
355 
356     command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
357     VerifyOrExit(command != nullptr);
358 
359     error = (this->*command->mHandler)(aArgs + 1);
360 
361 exit:
362     return error;
363 }
364 
ParseLinkMetricsFlags(otLinkMetrics & aLinkMetrics,const Arg & aFlags)365 otError LinkMetrics::ParseLinkMetricsFlags(otLinkMetrics &aLinkMetrics, const Arg &aFlags)
366 {
367     otError error = OT_ERROR_NONE;
368 
369     VerifyOrExit(!aFlags.IsEmpty(), error = OT_ERROR_INVALID_ARGS);
370 
371     ClearAllBytes(aLinkMetrics);
372 
373     for (const char *arg = aFlags.GetCString(); *arg != '\0'; arg++)
374     {
375         switch (*arg)
376         {
377         case 'p':
378             aLinkMetrics.mPduCount = true;
379             break;
380 
381         case 'q':
382             aLinkMetrics.mLqi = true;
383             break;
384 
385         case 'm':
386             aLinkMetrics.mLinkMargin = true;
387             break;
388 
389         case 'r':
390             aLinkMetrics.mRssi = true;
391             break;
392 
393         default:
394             ExitNow(error = OT_ERROR_INVALID_ARGS);
395         }
396     }
397 
398 exit:
399     return error;
400 }
401 
HandleLinkMetricsReport(const otIp6Address * aAddress,const otLinkMetricsValues * aMetricsValues,otLinkMetricsStatus aStatus,void * aContext)402 void LinkMetrics::HandleLinkMetricsReport(const otIp6Address        *aAddress,
403                                           const otLinkMetricsValues *aMetricsValues,
404                                           otLinkMetricsStatus        aStatus,
405                                           void                      *aContext)
406 {
407     static_cast<LinkMetrics *>(aContext)->HandleLinkMetricsReport(aAddress, aMetricsValues, aStatus);
408 }
409 
PrintLinkMetricsValue(const otLinkMetricsValues * aMetricsValues)410 void LinkMetrics::PrintLinkMetricsValue(const otLinkMetricsValues *aMetricsValues)
411 {
412     static const char kLinkMetricsTypeAverage[] = "(Exponential Moving Average)";
413 
414     if (aMetricsValues->mMetrics.mPduCount)
415     {
416         OutputLine(" - PDU Counter: %lu (Count/Summation)", ToUlong(aMetricsValues->mPduCountValue));
417     }
418 
419     if (aMetricsValues->mMetrics.mLqi)
420     {
421         OutputLine(" - LQI: %u %s", aMetricsValues->mLqiValue, kLinkMetricsTypeAverage);
422     }
423 
424     if (aMetricsValues->mMetrics.mLinkMargin)
425     {
426         OutputLine(" - Margin: %u (dB) %s", aMetricsValues->mLinkMarginValue, kLinkMetricsTypeAverage);
427     }
428 
429     if (aMetricsValues->mMetrics.mRssi)
430     {
431         OutputLine(" - RSSI: %d (dBm) %s", aMetricsValues->mRssiValue, kLinkMetricsTypeAverage);
432     }
433 }
434 
HandleLinkMetricsReport(const otIp6Address * aAddress,const otLinkMetricsValues * aMetricsValues,otLinkMetricsStatus aStatus)435 void LinkMetrics::HandleLinkMetricsReport(const otIp6Address        *aAddress,
436                                           const otLinkMetricsValues *aMetricsValues,
437                                           otLinkMetricsStatus        aStatus)
438 {
439     OutputFormat("Received Link Metrics Report from: ");
440     OutputIp6AddressLine(*aAddress);
441 
442     if (aMetricsValues != nullptr)
443     {
444         PrintLinkMetricsValue(aMetricsValues);
445     }
446     else
447     {
448         OutputLine("Link Metrics Report, status: %s", LinkMetricsStatusToStr(aStatus));
449     }
450 
451     if (mLinkMetricsQueryInProgress)
452     {
453         mLinkMetricsQueryInProgress = false;
454         OutputResult(OT_ERROR_NONE);
455     }
456 }
457 
HandleLinkMetricsMgmtResponse(const otIp6Address * aAddress,otLinkMetricsStatus aStatus,void * aContext)458 void LinkMetrics::HandleLinkMetricsMgmtResponse(const otIp6Address *aAddress,
459                                                 otLinkMetricsStatus aStatus,
460                                                 void               *aContext)
461 {
462     static_cast<LinkMetrics *>(aContext)->HandleLinkMetricsMgmtResponse(aAddress, aStatus);
463 }
464 
HandleLinkMetricsMgmtResponse(const otIp6Address * aAddress,otLinkMetricsStatus aStatus)465 void LinkMetrics::HandleLinkMetricsMgmtResponse(const otIp6Address *aAddress, otLinkMetricsStatus aStatus)
466 {
467     OutputFormat("Received Link Metrics Management Response from: ");
468     OutputIp6AddressLine(*aAddress);
469 
470     OutputLine("Status: %s", LinkMetricsStatusToStr(aStatus));
471 }
472 
HandleLinkMetricsEnhAckProbingIe(otShortAddress aShortAddress,const otExtAddress * aExtAddress,const otLinkMetricsValues * aMetricsValues,void * aContext)473 void LinkMetrics::HandleLinkMetricsEnhAckProbingIe(otShortAddress             aShortAddress,
474                                                    const otExtAddress        *aExtAddress,
475                                                    const otLinkMetricsValues *aMetricsValues,
476                                                    void                      *aContext)
477 {
478     static_cast<LinkMetrics *>(aContext)->HandleLinkMetricsEnhAckProbingIe(aShortAddress, aExtAddress, aMetricsValues);
479 }
480 
HandleLinkMetricsEnhAckProbingIe(otShortAddress aShortAddress,const otExtAddress * aExtAddress,const otLinkMetricsValues * aMetricsValues)481 void LinkMetrics::HandleLinkMetricsEnhAckProbingIe(otShortAddress             aShortAddress,
482                                                    const otExtAddress        *aExtAddress,
483                                                    const otLinkMetricsValues *aMetricsValues)
484 {
485     OutputFormat("Received Link Metrics data in Enh Ack from neighbor, short address:0x%02x , extended address:",
486                  aShortAddress);
487     OutputExtAddressLine(*aExtAddress);
488 
489     if (aMetricsValues != nullptr)
490     {
491         PrintLinkMetricsValue(aMetricsValues);
492     }
493 }
494 
LinkMetricsStatusToStr(otLinkMetricsStatus aStatus)495 const char *LinkMetrics::LinkMetricsStatusToStr(otLinkMetricsStatus aStatus)
496 {
497     static const char *const kStatusStrings[] = {
498         "Success",                      // (0) OT_LINK_METRICS_STATUS_SUCCESS
499         "Cannot support new series",    // (1) OT_LINK_METRICS_STATUS_CANNOT_SUPPORT_NEW_SERIES
500         "Series ID already registered", // (2) OT_LINK_METRICS_STATUS_SERIESID_ALREADY_REGISTERED
501         "Series ID not recognized",     // (3) OT_LINK_METRICS_STATUS_SERIESID_NOT_RECOGNIZED
502         "No matching series ID",        // (4) OT_LINK_METRICS_STATUS_NO_MATCHING_FRAMES_RECEIVED
503     };
504 
505     const char *str = "Unknown error";
506 
507     static_assert(0 == OT_LINK_METRICS_STATUS_SUCCESS, "STATUS_SUCCESS is incorrect");
508     static_assert(1 == OT_LINK_METRICS_STATUS_CANNOT_SUPPORT_NEW_SERIES, "CANNOT_SUPPORT_NEW_SERIES is incorrect");
509     static_assert(2 == OT_LINK_METRICS_STATUS_SERIESID_ALREADY_REGISTERED, "SERIESID_ALREADY_REGISTERED is incorrect");
510     static_assert(3 == OT_LINK_METRICS_STATUS_SERIESID_NOT_RECOGNIZED, "SERIESID_NOT_RECOGNIZED is incorrect");
511     static_assert(4 == OT_LINK_METRICS_STATUS_NO_MATCHING_FRAMES_RECEIVED, "NO_MATCHING_FRAMES_RECEIVED is incorrect");
512 
513     if (aStatus < OT_ARRAY_LENGTH(kStatusStrings))
514     {
515         str = kStatusStrings[aStatus];
516     }
517     else if (aStatus == OT_LINK_METRICS_STATUS_OTHER_ERROR)
518     {
519         str = "Other error";
520     }
521 
522     return str;
523 }
524 
OutputResult(otError aError)525 void LinkMetrics::OutputResult(otError aError) { Interpreter::GetInterpreter().OutputResult(aError); }
526 
527 } // namespace Cli
528 } // namespace ot
529 
530 #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
531