• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2025, 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 Mesh Diagnostics function.
32  */
33 
34 #include "cli_mesh_diag.hpp"
35 
36 #include <openthread/mesh_diag.h>
37 
38 #include "cli/cli.hpp"
39 #include "cli/cli_utils.hpp"
40 #include "common/code_utils.hpp"
41 
42 #if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD
43 
44 namespace ot {
45 namespace Cli {
46 
MeshDiag(otInstance * aInstance,OutputImplementer & aOutputImplementer)47 MeshDiag::MeshDiag(otInstance *aInstance, OutputImplementer &aOutputImplementer)
48     : Utils(aInstance, aOutputImplementer)
49 {
50 }
51 
Process(Arg aArgs[])52 template <> otError MeshDiag::Process<Cmd("topology")>(Arg aArgs[])
53 {
54     /**
55      * @cli meshdiag topology
56      * @code
57      * meshdiag topology
58      * id:02 rloc16:0x0800 ext-addr:8aa57d2c603fe16c ver:4 - me - leader
59      *    3-links:{ 46 }
60      * id:46 rloc16:0xb800 ext-addr:fe109d277e0175cc ver:4
61      *    3-links:{ 02 51 57 }
62      * id:33 rloc16:0x8400 ext-addr:d2e511a146b9e54d ver:4
63      *    3-links:{ 51 57 }
64      * id:51 rloc16:0xcc00 ext-addr:9aab43ababf05352 ver:4
65      *    3-links:{ 33 57 }
66      *    2-links:{ 46 }
67      * id:57 rloc16:0xe400 ext-addr:dae9c4c0e9da55ff ver:4
68      *    3-links:{ 46 51 }
69      *    1-links:{ 33 }
70      * Done
71      * @endcode
72      * @par
73      * Discover network topology (list of routers and their connections).
74      * Parameters are optional and indicate additional items to discover. Can be added in any order.
75      * * `ip6-addrs` to discover the list of IPv6 addresses of every router.
76      * * `children` to discover the child table of every router.
77      * @par
78      * Information per router:
79      * * Router ID
80      * * RLOC16
81      * * Extended MAC address
82      * * Thread Version (if known)
83      * * Whether the router is this device is itself (`me`)
84      * * Whether the router is the parent of this device when device is a child (`parent`)
85      * * Whether the router is `leader`
86      * * Whether the router acts as a border router providing external connectivity (`br`)
87      * * List of routers to which this router has a link:
88      *   * `3-links`: Router IDs to which this router has a incoming link with link quality 3
89      *   * `2-links`: Router IDs to which this router has a incoming link with link quality 2
90      *   * `1-links`: Router IDs to which this router has a incoming link with link quality 1
91      *   * If a list if empty, it is omitted in the out.
92      * * If `ip6-addrs`, list of IPv6 addresses of the router
93      * * If `children`, list of all children of the router. Information per child:
94      *   * RLOC16
95      *   * Incoming Link Quality from perspective of parent to child (zero indicates unknown)
96      *   * Child Device mode (`r` rx-on-when-idle, `d` Full Thread Device, `n` Full Network Data, `-` no flags set)
97      *   * Whether the child is this device itself (`me`)
98      *   * Whether the child acts as a border router providing external connectivity (`br`)
99      * @cparam meshdiag topology [@ca{ip6-addrs}] [@ca{children}]
100      * @sa otMeshDiagDiscoverTopology
101      */
102     otError                  error = OT_ERROR_NONE;
103     otMeshDiagDiscoverConfig config;
104 
105     config.mDiscoverIp6Addresses = false;
106     config.mDiscoverChildTable   = false;
107 
108     for (; !aArgs->IsEmpty(); aArgs++)
109     {
110         if (*aArgs == "ip6-addrs")
111         {
112             config.mDiscoverIp6Addresses = true;
113         }
114         else if (*aArgs == "children")
115         {
116             config.mDiscoverChildTable = true;
117         }
118         else
119         {
120             ExitNow(error = OT_ERROR_INVALID_ARGS);
121         }
122     }
123 
124     SuccessOrExit(error = otMeshDiagDiscoverTopology(GetInstancePtr(), &config, HandleMeshDiagDiscoverDone, this));
125     error = OT_ERROR_PENDING;
126 
127 exit:
128     return error;
129 }
130 
Process(Arg aArgs[])131 template <> otError MeshDiag::Process<Cmd("childtable")>(Arg aArgs[])
132 {
133     /**
134      * @cli meshdiag childtable
135      * @code
136      * meshdiag childtable 0x6400
137      * rloc16:0x6402 ext-addr:8e6f4d323bbed1fe ver:4
138      *     timeout:120 age:36 supvn:129 q-msg:0
139      *     rx-on:yes type:ftd full-net:yes
140      *     rss - ave:-20 last:-20 margin:80
141      *     err-rate - frame:11.51% msg:0.76%
142      *     conn-time:00:11:07
143      *     csl - sync:no period:0 timeout:0 channel:0
144      * rloc16:0x6403 ext-addr:ee24e64ecf8c079a ver:4
145      *     timeout:120 age:19 supvn:129 q-msg:0
146      *     rx-on:no type:mtd full-net:no
147      *     rss - ave:-20 last:-20  margin:80
148      *     err-rate - frame:0.73% msg:0.00%
149      *     conn-time:01:08:53
150      *     csl - sync:no period:0 timeout:0 channel:0
151      * Done
152      * @endcode
153      * @par
154      * Start a query for child table of a router with a given RLOC16.
155      * Output lists all child entries. Information per child:
156      * - RLOC16
157      * - Extended MAC address
158      * - Thread Version
159      * - Timeout (in seconds)
160      * - Age (seconds since last heard)
161      * - Supervision interval (in seconds)
162      * - Number of queued messages (in case child is sleepy)
163      * - Device Mode
164      * - RSS (average and last)
165      * - Error rates: frame tx (at MAC layer), IPv6 message tx (above MAC)
166      * - Connection time (seconds since link establishment `{dd}d.{hh}:{mm}:{ss}` format)
167      * - CSL info:
168      *   - If synchronized
169      *   - Period (in unit of 10-symbols-time)
170      *   - Timeout (in seconds)
171      *
172      * @cparam meshdiag childtable @ca{router-rloc16}
173      * @sa otMeshDiagQueryChildTable
174      */
175     otError  error = OT_ERROR_NONE;
176     uint16_t routerRloc16;
177 
178     SuccessOrExit(error = aArgs[0].ParseAsUint16(routerRloc16));
179     VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
180 
181     SuccessOrExit(
182         error = otMeshDiagQueryChildTable(GetInstancePtr(), routerRloc16, HandleMeshDiagQueryChildTableResult, this));
183 
184     error = OT_ERROR_PENDING;
185 
186 exit:
187     return error;
188 }
189 
Process(Arg aArgs[])190 template <> otError MeshDiag::Process<Cmd("childip6")>(Arg aArgs[])
191 {
192     /**
193      * @cli meshdiag childip6
194      * @code
195      * meshdiag childip6 0xdc00
196      * child-rloc16: 0xdc02
197      *     fdde:ad00:beef:0:ded8:cd58:b73:2c21
198      *     fd00:2:0:0:c24a:456:3b6b:c597
199      *     fd00:1:0:0:120b:95fe:3ecc:d238
200      * child-rloc16: 0xdc03
201      *     fdde:ad00:beef:0:3aa6:b8bf:e7d6:eefe
202      *     fd00:2:0:0:8ff8:a188:7436:6720
203      *     fd00:1:0:0:1fcf:5495:790a:370f
204      * Done
205      * @endcode
206      * @par
207      * Send a query to a parent to retrieve the IPv6 addresses of all its MTD children.
208      * @cparam meshdiag childip6 @ca{parent-rloc16}
209      * @sa otMeshDiagQueryChildrenIp6Addrs
210      */
211     otError  error = OT_ERROR_NONE;
212     uint16_t parentRloc16;
213 
214     SuccessOrExit(error = aArgs[0].ParseAsUint16(parentRloc16));
215     VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
216 
217     SuccessOrExit(error = otMeshDiagQueryChildrenIp6Addrs(GetInstancePtr(), parentRloc16,
218                                                           HandleMeshDiagQueryChildIp6Addrs, this));
219 
220     error = OT_ERROR_PENDING;
221 
222 exit:
223     return error;
224 }
225 
Process(Arg aArgs[])226 template <> otError MeshDiag::Process<Cmd("routerneighbortable")>(Arg aArgs[])
227 {
228     /**
229      * @cli meshdiag routerneighbortable
230      * @code
231      * meshdiag routerneighbortable 0x7400
232      * rloc16:0x9c00 ext-addr:764788cf6e57a4d2 ver:4
233      *    rss - ave:-20 last:-20 margin:80
234      *    err-rate - frame:1.38% msg:0.00%
235      *    conn-time:01:54:02
236      * rloc16:0x7c00 ext-addr:4ed24fceec9bf6d3 ver:4
237      *    rss - ave:-20 last:-20 margin:80
238      *    err-rate - frame:0.72% msg:0.00%
239      *    conn-time:00:11:27
240      * Done
241      * @endcode
242      * @par
243      * Start a query for router neighbor table of a router with a given RLOC16.
244      * Output lists all router neighbor entries. Information per entry:
245      *  - RLOC16
246      *  - Extended MAC address
247      *  - Thread Version
248      *  - RSS (average and last) and link margin
249      *  - Error rates, frame tx (at MAC layer), IPv6 message tx (above MAC)
250      *  - Connection time (seconds since link establishment `{dd}d.{hh}:{mm}:{ss}` format)
251      * @cparam meshdiag routerneighbortable @ca{router-rloc16}
252      * @sa otMeshDiagQueryRouterNeighborTable
253      */
254     otError  error = OT_ERROR_NONE;
255     uint16_t routerRloc16;
256 
257     SuccessOrExit(error = aArgs[0].ParseAsUint16(routerRloc16));
258     VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
259 
260     SuccessOrExit(error = otMeshDiagQueryRouterNeighborTable(GetInstancePtr(), routerRloc16,
261                                                              HandleMeshDiagQueryRouterNeighborTableResult, this));
262 
263     error = OT_ERROR_PENDING;
264 
265 exit:
266     return error;
267 }
268 
Process(Arg aArgs[])269 otError MeshDiag::Process(Arg aArgs[])
270 {
271 #define CmdEntry(aCommandString)                                \
272     {                                                           \
273         aCommandString, &MeshDiag::Process<Cmd(aCommandString)> \
274     }
275 
276     static constexpr Command kCommands[] = {
277         CmdEntry("childip6"),
278         CmdEntry("childtable"),
279         CmdEntry("routerneighbortable"),
280         CmdEntry("topology"),
281     };
282 
283     static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
284 
285     otError        error = OT_ERROR_INVALID_COMMAND;
286     const Command *command;
287 
288     if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
289     {
290         OutputCommandTable(kCommands);
291         ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
292     }
293 
294     command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
295     VerifyOrExit(command != nullptr);
296 
297     error = (this->*command->mHandler)(aArgs + 1);
298 
299 exit:
300     return error;
301 }
302 
HandleMeshDiagDiscoverDone(otError aError,otMeshDiagRouterInfo * aRouterInfo,void * aContext)303 void MeshDiag::HandleMeshDiagDiscoverDone(otError aError, otMeshDiagRouterInfo *aRouterInfo, void *aContext)
304 {
305     reinterpret_cast<MeshDiag *>(aContext)->HandleMeshDiagDiscoverDone(aError, aRouterInfo);
306 }
307 
HandleMeshDiagDiscoverDone(otError aError,otMeshDiagRouterInfo * aRouterInfo)308 void MeshDiag::HandleMeshDiagDiscoverDone(otError aError, otMeshDiagRouterInfo *aRouterInfo)
309 {
310     VerifyOrExit(aRouterInfo != nullptr);
311 
312     OutputFormat("id:%02u rloc16:0x%04x ext-addr:", aRouterInfo->mRouterId, aRouterInfo->mRloc16);
313     OutputExtAddress(aRouterInfo->mExtAddress);
314 
315     if (aRouterInfo->mVersion != OT_MESH_DIAG_VERSION_UNKNOWN)
316     {
317         OutputFormat(" ver:%u", aRouterInfo->mVersion);
318     }
319 
320     if (aRouterInfo->mIsThisDevice)
321     {
322         OutputFormat(" - me");
323     }
324 
325     if (aRouterInfo->mIsThisDeviceParent)
326     {
327         OutputFormat(" - parent");
328     }
329 
330     if (aRouterInfo->mIsLeader)
331     {
332         OutputFormat(" - leader");
333     }
334 
335     if (aRouterInfo->mIsBorderRouter)
336     {
337         OutputFormat(" - br");
338     }
339 
340     OutputNewLine();
341 
342     for (uint8_t linkQuality = 3; linkQuality > 0; linkQuality--)
343     {
344         bool hasLinkQuality = false;
345 
346         for (uint8_t entryQuality : aRouterInfo->mLinkQualities)
347         {
348             if (entryQuality == linkQuality)
349             {
350                 hasLinkQuality = true;
351                 break;
352             }
353         }
354 
355         if (hasLinkQuality)
356         {
357             OutputFormat(kIndentSize, "%u-links:{ ", linkQuality);
358 
359             for (uint8_t id = 0; id < static_cast<uint8_t>(OT_ARRAY_LENGTH(aRouterInfo->mLinkQualities)); id++)
360             {
361                 if (aRouterInfo->mLinkQualities[id] == linkQuality)
362                 {
363                     OutputFormat("%02u ", id);
364                 }
365             }
366 
367             OutputLine("}");
368         }
369     }
370 
371     if (aRouterInfo->mIp6AddrIterator != nullptr)
372     {
373         otIp6Address ip6Address;
374 
375         OutputLine(kIndentSize, "ip6-addrs:");
376 
377         while (otMeshDiagGetNextIp6Address(aRouterInfo->mIp6AddrIterator, &ip6Address) == OT_ERROR_NONE)
378         {
379             OutputSpaces(kIndentSize * 2);
380             OutputIp6AddressLine(ip6Address);
381         }
382     }
383 
384     if (aRouterInfo->mChildIterator != nullptr)
385     {
386         otMeshDiagChildInfo childInfo;
387         char                linkModeString[kLinkModeStringSize];
388         bool                isFirst = true;
389 
390         while (otMeshDiagGetNextChildInfo(aRouterInfo->mChildIterator, &childInfo) == OT_ERROR_NONE)
391         {
392             if (isFirst)
393             {
394                 OutputLine(kIndentSize, "children:");
395                 isFirst = false;
396             }
397 
398             OutputFormat(kIndentSize * 2, "rloc16:0x%04x lq:%u, mode:%s", childInfo.mRloc16, childInfo.mLinkQuality,
399                          LinkModeToString(childInfo.mMode, linkModeString));
400 
401             if (childInfo.mIsThisDevice)
402             {
403                 OutputFormat(" - me");
404             }
405 
406             if (childInfo.mIsBorderRouter)
407             {
408                 OutputFormat(" - br");
409             }
410 
411             OutputNewLine();
412         }
413 
414         if (isFirst)
415         {
416             OutputLine(kIndentSize, "children: none");
417         }
418     }
419 
420 exit:
421     OutputResult(aError);
422 }
423 
HandleMeshDiagQueryChildTableResult(otError aError,const otMeshDiagChildEntry * aChildEntry,void * aContext)424 void MeshDiag::HandleMeshDiagQueryChildTableResult(otError                     aError,
425                                                    const otMeshDiagChildEntry *aChildEntry,
426                                                    void                       *aContext)
427 {
428     reinterpret_cast<MeshDiag *>(aContext)->HandleMeshDiagQueryChildTableResult(aError, aChildEntry);
429 }
430 
HandleMeshDiagQueryChildTableResult(otError aError,const otMeshDiagChildEntry * aChildEntry)431 void MeshDiag::HandleMeshDiagQueryChildTableResult(otError aError, const otMeshDiagChildEntry *aChildEntry)
432 {
433     PercentageStringBuffer stringBuffer;
434     char                   string[OT_DURATION_STRING_SIZE];
435 
436     VerifyOrExit(aChildEntry != nullptr);
437 
438     OutputFormat("rloc16:0x%04x ext-addr:", aChildEntry->mRloc16);
439     OutputExtAddress(aChildEntry->mExtAddress);
440     OutputLine(" ver:%u", aChildEntry->mVersion);
441 
442     OutputLine(kIndentSize, "timeout:%lu age:%lu supvn:%u q-msg:%u", ToUlong(aChildEntry->mTimeout),
443                ToUlong(aChildEntry->mAge), aChildEntry->mSupervisionInterval, aChildEntry->mQueuedMessageCount);
444 
445     OutputLine(kIndentSize, "rx-on:%s type:%s full-net:%s", aChildEntry->mRxOnWhenIdle ? "yes" : "no",
446                aChildEntry->mDeviceTypeFtd ? "ftd" : "mtd", aChildEntry->mFullNetData ? "yes" : "no");
447 
448     OutputLine(kIndentSize, "rss - ave:%d last:%d margin:%d", aChildEntry->mAverageRssi, aChildEntry->mLastRssi,
449                aChildEntry->mLinkMargin);
450 
451     if (aChildEntry->mSupportsErrRate)
452     {
453         OutputFormat(kIndentSize, "err-rate - frame:%s%% ",
454                      PercentageToString(aChildEntry->mFrameErrorRate, stringBuffer));
455         OutputLine("msg:%s%% ", PercentageToString(aChildEntry->mMessageErrorRate, stringBuffer));
456     }
457 
458     otConvertDurationInSecondsToString(aChildEntry->mConnectionTime, string, sizeof(string));
459     OutputLine(kIndentSize, "conn-time:%s", string);
460 
461     OutputLine(kIndentSize, "csl - sync:%s period:%u timeout:%lu channel:%u",
462                aChildEntry->mCslSynchronized ? "yes" : "no", aChildEntry->mCslPeriod, ToUlong(aChildEntry->mCslTimeout),
463                aChildEntry->mCslChannel);
464 
465 exit:
466     OutputResult(aError);
467 }
468 
HandleMeshDiagQueryRouterNeighborTableResult(otError aError,const otMeshDiagRouterNeighborEntry * aNeighborEntry,void * aContext)469 void MeshDiag::HandleMeshDiagQueryRouterNeighborTableResult(otError                              aError,
470                                                             const otMeshDiagRouterNeighborEntry *aNeighborEntry,
471                                                             void                                *aContext)
472 {
473     reinterpret_cast<MeshDiag *>(aContext)->HandleMeshDiagQueryRouterNeighborTableResult(aError, aNeighborEntry);
474 }
475 
HandleMeshDiagQueryRouterNeighborTableResult(otError aError,const otMeshDiagRouterNeighborEntry * aNeighborEntry)476 void MeshDiag::HandleMeshDiagQueryRouterNeighborTableResult(otError                              aError,
477                                                             const otMeshDiagRouterNeighborEntry *aNeighborEntry)
478 {
479     PercentageStringBuffer stringBuffer;
480     char                   string[OT_DURATION_STRING_SIZE];
481 
482     VerifyOrExit(aNeighborEntry != nullptr);
483 
484     OutputFormat("rloc16:0x%04x ext-addr:", aNeighborEntry->mRloc16);
485     OutputExtAddress(aNeighborEntry->mExtAddress);
486     OutputLine(" ver:%u", aNeighborEntry->mVersion);
487 
488     OutputLine(kIndentSize, "rss - ave:%d last:%d margin:%d", aNeighborEntry->mAverageRssi, aNeighborEntry->mLastRssi,
489                aNeighborEntry->mLinkMargin);
490 
491     if (aNeighborEntry->mSupportsErrRate)
492     {
493         OutputFormat(kIndentSize, "err-rate - frame:%s%% ",
494                      PercentageToString(aNeighborEntry->mFrameErrorRate, stringBuffer));
495         OutputLine("msg:%s%% ", PercentageToString(aNeighborEntry->mMessageErrorRate, stringBuffer));
496     }
497 
498     otConvertDurationInSecondsToString(aNeighborEntry->mConnectionTime, string, sizeof(string));
499     OutputLine(kIndentSize, "conn-time:%s", string);
500 
501 exit:
502     OutputResult(aError);
503 }
504 
HandleMeshDiagQueryChildIp6Addrs(otError aError,uint16_t aChildRloc16,otMeshDiagIp6AddrIterator * aIp6AddrIterator,void * aContext)505 void MeshDiag::HandleMeshDiagQueryChildIp6Addrs(otError                    aError,
506                                                 uint16_t                   aChildRloc16,
507                                                 otMeshDiagIp6AddrIterator *aIp6AddrIterator,
508                                                 void                      *aContext)
509 {
510     reinterpret_cast<MeshDiag *>(aContext)->HandleMeshDiagQueryChildIp6Addrs(aError, aChildRloc16, aIp6AddrIterator);
511 }
512 
HandleMeshDiagQueryChildIp6Addrs(otError aError,uint16_t aChildRloc16,otMeshDiagIp6AddrIterator * aIp6AddrIterator)513 void MeshDiag::HandleMeshDiagQueryChildIp6Addrs(otError                    aError,
514                                                 uint16_t                   aChildRloc16,
515                                                 otMeshDiagIp6AddrIterator *aIp6AddrIterator)
516 {
517     otIp6Address ip6Address;
518 
519     VerifyOrExit(aError == OT_ERROR_NONE || aError == OT_ERROR_PENDING);
520     VerifyOrExit(aIp6AddrIterator != nullptr);
521 
522     OutputLine("child-rloc16: 0x%04x", aChildRloc16);
523 
524     while (otMeshDiagGetNextIp6Address(aIp6AddrIterator, &ip6Address) == OT_ERROR_NONE)
525     {
526         OutputSpaces(kIndentSize);
527         OutputIp6AddressLine(ip6Address);
528     }
529 
530 exit:
531     OutputResult(aError);
532 }
533 
OutputResult(otError aError)534 void MeshDiag::OutputResult(otError aError) { Interpreter::GetInterpreter().OutputResult(aError); }
535 
536 } // namespace Cli
537 } // namespace ot
538 
539 #endif // OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD
540