• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2020, 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 CLI commands for Network Data.
32  */
33 
34 #include "cli_network_data.hpp"
35 
36 #include <openthread/border_router.h>
37 #include <openthread/netdata_publisher.h>
38 #include <openthread/server.h>
39 
40 #include "cli/cli.hpp"
41 #include "common/encoding.hpp"
42 
43 namespace ot {
44 namespace Cli {
45 
PrefixFlagsToString(const otBorderRouterConfig & aConfig,FlagsString & aString)46 void NetworkData::PrefixFlagsToString(const otBorderRouterConfig &aConfig, FlagsString &aString)
47 {
48     char *flagsPtr = &aString[0];
49 
50     if (aConfig.mPreferred)
51     {
52         *flagsPtr++ = 'p';
53     }
54 
55     if (aConfig.mSlaac)
56     {
57         *flagsPtr++ = 'a';
58     }
59 
60     if (aConfig.mDhcp)
61     {
62         *flagsPtr++ = 'd';
63     }
64 
65     if (aConfig.mConfigure)
66     {
67         *flagsPtr++ = 'c';
68     }
69 
70     if (aConfig.mDefaultRoute)
71     {
72         *flagsPtr++ = 'r';
73     }
74 
75     if (aConfig.mOnMesh)
76     {
77         *flagsPtr++ = 'o';
78     }
79 
80     if (aConfig.mStable)
81     {
82         *flagsPtr++ = 's';
83     }
84 
85     if (aConfig.mNdDns)
86     {
87         *flagsPtr++ = 'n';
88     }
89 
90     if (aConfig.mDp)
91     {
92         *flagsPtr++ = 'D';
93     }
94 
95     *flagsPtr = '\0';
96 }
97 
OutputPrefix(const otBorderRouterConfig & aConfig)98 void NetworkData::OutputPrefix(const otBorderRouterConfig &aConfig)
99 {
100     FlagsString flagsString;
101 
102     OutputIp6Prefix(aConfig.mPrefix);
103 
104     PrefixFlagsToString(aConfig, flagsString);
105 
106     if (flagsString[0] != '\0')
107     {
108         OutputFormat(" %s", flagsString);
109     }
110 
111     OutputLine(" %s %04x", Interpreter::PreferenceToString(aConfig.mPreference), aConfig.mRloc16);
112 }
113 
RouteFlagsToString(const otExternalRouteConfig & aConfig,FlagsString & aString)114 void NetworkData::RouteFlagsToString(const otExternalRouteConfig &aConfig, FlagsString &aString)
115 {
116     char *flagsPtr = &aString[0];
117 
118     if (aConfig.mStable)
119     {
120         *flagsPtr++ = 's';
121     }
122 
123     if (aConfig.mNat64)
124     {
125         *flagsPtr++ = 'n';
126     }
127 
128     *flagsPtr = '\0';
129 }
130 
OutputRoute(const otExternalRouteConfig & aConfig)131 void NetworkData::OutputRoute(const otExternalRouteConfig &aConfig)
132 {
133     FlagsString flagsString;
134 
135     OutputIp6Prefix(aConfig.mPrefix);
136 
137     RouteFlagsToString(aConfig, flagsString);
138 
139     if (flagsString[0] != '\0')
140     {
141         OutputFormat(" %s", flagsString);
142     }
143 
144     OutputLine(" %s %04x", Interpreter::PreferenceToString(aConfig.mPreference), aConfig.mRloc16);
145 }
146 
OutputService(const otServiceConfig & aConfig)147 void NetworkData::OutputService(const otServiceConfig &aConfig)
148 {
149     OutputFormat("%u ", aConfig.mEnterpriseNumber);
150     OutputBytes(aConfig.mServiceData, aConfig.mServiceDataLength);
151     OutputFormat(" ");
152     OutputBytes(aConfig.mServerConfig.mServerData, aConfig.mServerConfig.mServerDataLength);
153 
154     if (aConfig.mServerConfig.mStable)
155     {
156         OutputFormat(" s");
157     }
158 
159     OutputLine(" %04x", aConfig.mServerConfig.mRloc16);
160 }
161 
162 #if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE
Process(Arg aArgs[])163 template <> otError NetworkData::Process<Cmd("publish")>(Arg aArgs[])
164 {
165     otError error = OT_ERROR_NONE;
166 
167 #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
168     if (aArgs[0] == "dnssrp")
169     {
170         /**
171          * @cli netdata publish dnssrp anycast
172          * @code
173          * netdata publish dnssrp anycast 1
174          * Done
175          * @endcode
176          * @cparam netdata publish dnssrp anycast @ca{seq-num}
177          * @par
178          * Publishes a DNS/SRP Service Anycast Address with a sequence number. Any current
179          * DNS/SRP Service entry being published from a previous `publish dnssrp{anycast|unicast}`
180          * command is removed and replaced with the new arguments.
181          * @par
182          * `OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE` must be enabled.
183          * @csa{netdata publish dnssrp unicast (addr,port)}
184          * @csa{netdata publish dnssrp unicast (mle)}
185          * @sa otNetDataPublishDnsSrpServiceAnycast
186          * @endcli
187          */
188         if (aArgs[1] == "anycast")
189         {
190             uint8_t sequenceNumber;
191 
192             SuccessOrExit(error = aArgs[2].ParseAsUint8(sequenceNumber));
193             otNetDataPublishDnsSrpServiceAnycast(GetInstancePtr(), sequenceNumber);
194             ExitNow();
195         }
196 
197         if (aArgs[1] == "unicast")
198         {
199             otIp6Address address;
200             uint16_t     port;
201 
202             /**
203              * @cli netdata publish dnssrp unicast (mle)
204              * @code
205              * netdata publish dnssrp unicast 50152
206              * Done
207              * @endcode
208              * @cparam netdata publish dnssrp unicast @ca{port}
209              * @par
210              * Publishes the device's Mesh-Local EID with a port number. MLE and port information is
211              * included in the Server TLV data. To use a different Unicast address, use the
212              * `netdata publish dnssrp unicast (addr,port)` command.
213              * @par
214              * Any current DNS/SRP Service entry being published from a previous
215              * `publish dnssrp{anycast|unicast}` command is removed and replaced with the new arguments.
216              * @par
217              * `OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE` must be enabled.
218              * @csa{netdata publish dnssrp unicast (addr,port)}
219              * @csa{netdata publish dnssrp anycast}
220              * @sa otNetDataPublishDnsSrpServiceUnicastMeshLocalEid
221              */
222             if (aArgs[3].IsEmpty())
223             {
224                 SuccessOrExit(error = aArgs[2].ParseAsUint16(port));
225                 otNetDataPublishDnsSrpServiceUnicastMeshLocalEid(GetInstancePtr(), port);
226                 ExitNow();
227             }
228 
229             /**
230              * @cli netdata publish dnssrp unicast (addr,port)
231              * @code
232              * netdata publish dnssrp unicast fd00::1234 51525
233              * Done
234              * @endcode
235              * @cparam netdata publish dnssrp unicast @ca{address} @ca{port}
236              * @par
237              * Publishes a DNS/SRP Service Unicast Address with an address and port number. The address
238              * and port information is included in Service TLV data. Any current DNS/SRP Service entry being
239              * published from a previous `publish dnssrp{anycast|unicast}` command is removed and replaced
240              * with the new arguments.
241              * @par
242              * `OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE` must be enabled.
243              * @csa{netdata publish dnssrp unicast (mle)}
244              * @csa{netdata publish dnssrp anycast}
245              * @sa otNetDataPublishDnsSrpServiceUnicast
246              */
247             SuccessOrExit(error = aArgs[2].ParseAsIp6Address(address));
248             SuccessOrExit(error = aArgs[3].ParseAsUint16(port));
249             otNetDataPublishDnsSrpServiceUnicast(GetInstancePtr(), &address, port);
250             ExitNow();
251         }
252     }
253 #endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
254 
255 #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
256     /**
257      * @cli netdata publish prefix
258      * @code
259      * netdata publish prefix fd00:1234:5678::/64 paos med
260      * Done
261      * @endcode
262      * @cparam netdata publish prefix @ca{prefix} [@ca{padcrosnD}] [@ca{high}|@ca{med}|@ca{low}]
263      * OT CLI uses mapped arguments to configure #otBorderRouterConfig values. @moreinfo{the @overview}.
264      * @par
265      * Publish an on-mesh prefix entry. @moreinfo{@netdata}.
266      * @sa otNetDataPublishOnMeshPrefix
267      */
268     if (aArgs[0] == "prefix")
269     {
270         otBorderRouterConfig config;
271 
272         SuccessOrExit(error = Interpreter::ParsePrefix(aArgs + 1, config));
273         error = otNetDataPublishOnMeshPrefix(GetInstancePtr(), &config);
274         ExitNow();
275     }
276 
277     /**
278      * @cli netdata publish route
279      * @code
280      * netdata publish route fd00:1234:5678::/64 s high
281      * Done
282      * @endcode
283      * @cparam publish route @ca{prefix} [@ca{sn}] [@ca{high}|@ca{med}|@ca{low}]
284      * OT CLI uses mapped arguments to configure #otExternalRouteConfig values. @moreinfo{the @overview}.
285      * @par
286      * Publish an external route entry. @moreinfo{@netdata}.
287      * @sa otNetDataPublishExternalRoute
288      */
289     if (aArgs[0] == "route")
290     {
291         otExternalRouteConfig config;
292 
293         SuccessOrExit(error = Interpreter::ParseRoute(aArgs + 1, config));
294         error = otNetDataPublishExternalRoute(GetInstancePtr(), &config);
295         ExitNow();
296     }
297 #endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
298 
299     error = OT_ERROR_INVALID_ARGS;
300 
301 exit:
302     return error;
303 }
304 
Process(Arg aArgs[])305 template <> otError NetworkData::Process<Cmd("unpublish")>(Arg aArgs[])
306 {
307     otError error = OT_ERROR_NONE;
308 
309 /**
310  * @cli netdata unpublish dnssrp
311  * @code
312  * netdata unpublish dnssrp
313  * Done
314  * @endcode
315  * @par api_copy
316  * #otNetDataUnpublishDnsSrpService
317  */
318 #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
319     if (aArgs[0] == "dnssrp")
320     {
321         otNetDataUnpublishDnsSrpService(GetInstancePtr());
322         ExitNow();
323     }
324 #endif
325 
326 /**
327  * @cli netdata unpublish (prefix)
328  * @code
329  * netdata unpublish fd00:1234:5678::/64
330  * Done
331  * @endcode
332  * @cparam netdata unpublish @ca{prefix}
333  * @par api_copy
334  * #otNetDataUnpublishPrefix
335  * @par
336  * @moreinfo{@netdata}.
337  */
338 #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
339     {
340         otIp6Prefix prefix;
341 
342         if (aArgs[0].ParseAsIp6Prefix(prefix) == OT_ERROR_NONE)
343         {
344             error = otNetDataUnpublishPrefix(GetInstancePtr(), &prefix);
345             ExitNow();
346         }
347     }
348 #endif
349 
350     error = OT_ERROR_INVALID_ARGS;
351 
352 exit:
353     return error;
354 }
355 #endif // OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE
356 
357 #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
358 /**
359  * @cli netdata register
360  * @code
361  * netdata register
362  * Done
363  * @endcode
364  * @par
365  * Register configured prefixes, routes, and services with the Leader.
366  * @par
367  * OT CLI checks for `OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE`. If OTBR is enabled, it
368  * registers local Network Data with the Leader. Otherwise, it calls the CLI function `otServerRegister`.
369  * @moreinfo{@netdata}.
370  * @csa{prefix add}
371  * @sa otBorderRouterRegister
372  * @sa otServerAddService
373  */
Process(Arg aArgs[])374 template <> otError NetworkData::Process<Cmd("register")>(Arg aArgs[])
375 {
376     OT_UNUSED_VARIABLE(aArgs);
377 
378     otError error = OT_ERROR_NONE;
379 
380 #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
381     SuccessOrExit(error = otBorderRouterRegister(GetInstancePtr()));
382 #else
383     SuccessOrExit(error = otServerRegister(GetInstancePtr()));
384 #endif
385 
386 exit:
387     return error;
388 }
389 #endif
390 
Process(Arg aArgs[])391 template <> otError NetworkData::Process<Cmd("steeringdata")>(Arg aArgs[])
392 {
393     otError           error;
394     otExtAddress      addr;
395     otJoinerDiscerner discerner;
396 
397     VerifyOrExit(aArgs[0] == "check", error = OT_ERROR_INVALID_ARGS);
398 
399     error = Interpreter::ParseJoinerDiscerner(aArgs[1], discerner);
400 
401     if (error == OT_ERROR_NOT_FOUND)
402     {
403         discerner.mLength = 0;
404         error             = aArgs[1].ParseAsHexString(addr.m8);
405     }
406 
407     SuccessOrExit(error);
408 
409     /**
410      * @cli netdata steeringdata check (discerner)
411      * @code
412      * netdata steeringdata check 0xabc/12
413      * Done
414      * @endcode
415      * @code
416      * netdata steeringdata check 0xdef/12
417      * Error 23: NotFound
418      * @endcode
419      * @cparam netdata steeringdata check @ca{discerner}
420      * *   `discerner`: The %Joiner discerner in format `{number}/{length}`.
421      * @par api_copy
422      * #otNetDataSteeringDataCheckJoinerWithDiscerner
423      * @csa{joiner discerner}
424      */
425     if (discerner.mLength)
426     {
427         error = otNetDataSteeringDataCheckJoinerWithDiscerner(GetInstancePtr(), &discerner);
428     }
429     /**
430      * @cli netdata steeringdata check (eui64)
431      * @code
432      * netdata steeringdata check d45e64fa83f81cf7
433      * Done
434      * @endcode
435      * @cparam netdata steeringdata check @ca{eui64}
436      * *   `eui64`: The IEEE EUI-64 of the %Joiner.
437      * @par api_copy
438      * #otNetDataSteeringDataCheckJoiner
439      * @csa{eui64}
440      */
441     else
442     {
443         error = otNetDataSteeringDataCheckJoiner(GetInstancePtr(), &addr);
444     }
445 
446 exit:
447     return error;
448 }
449 
GetNextPrefix(otNetworkDataIterator * aIterator,otBorderRouterConfig * aConfig,bool aLocal)450 otError NetworkData::GetNextPrefix(otNetworkDataIterator *aIterator, otBorderRouterConfig *aConfig, bool aLocal)
451 {
452     otError error;
453 
454     if (aLocal)
455     {
456 #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
457         error = otBorderRouterGetNextOnMeshPrefix(GetInstancePtr(), aIterator, aConfig);
458 #else
459         error = OT_ERROR_NOT_FOUND;
460 #endif
461     }
462     else
463     {
464         error = otNetDataGetNextOnMeshPrefix(GetInstancePtr(), aIterator, aConfig);
465     }
466 
467     return error;
468 }
469 
OutputPrefixes(bool aLocal)470 void NetworkData::OutputPrefixes(bool aLocal)
471 {
472     otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT;
473     otBorderRouterConfig  config;
474 
475     OutputLine("Prefixes:");
476 
477     while (GetNextPrefix(&iterator, &config, aLocal) == OT_ERROR_NONE)
478     {
479         OutputPrefix(config);
480     }
481 }
482 
GetNextRoute(otNetworkDataIterator * aIterator,otExternalRouteConfig * aConfig,bool aLocal)483 otError NetworkData::GetNextRoute(otNetworkDataIterator *aIterator, otExternalRouteConfig *aConfig, bool aLocal)
484 {
485     otError error;
486 
487     if (aLocal)
488     {
489 #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
490         error = otBorderRouterGetNextRoute(GetInstancePtr(), aIterator, aConfig);
491 #else
492         error = OT_ERROR_NOT_FOUND;
493 #endif
494     }
495     else
496     {
497         error = otNetDataGetNextRoute(GetInstancePtr(), aIterator, aConfig);
498     }
499 
500     return error;
501 }
502 
OutputRoutes(bool aLocal)503 void NetworkData::OutputRoutes(bool aLocal)
504 {
505     otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT;
506     otExternalRouteConfig config;
507 
508     OutputLine("Routes:");
509 
510     while (GetNextRoute(&iterator, &config, aLocal) == OT_ERROR_NONE)
511     {
512         OutputRoute(config);
513     }
514 }
515 
GetNextService(otNetworkDataIterator * aIterator,otServiceConfig * aConfig,bool aLocal)516 otError NetworkData::GetNextService(otNetworkDataIterator *aIterator, otServiceConfig *aConfig, bool aLocal)
517 {
518     otError error;
519 
520     if (aLocal)
521     {
522 #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
523         error = otServerGetNextService(GetInstancePtr(), aIterator, aConfig);
524 #else
525         error = OT_ERROR_NOT_FOUND;
526 #endif
527     }
528     else
529     {
530         error = otNetDataGetNextService(GetInstancePtr(), aIterator, aConfig);
531     }
532 
533     return error;
534 }
535 
OutputServices(bool aLocal)536 void NetworkData::OutputServices(bool aLocal)
537 {
538     otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT;
539     otServiceConfig       config;
540 
541     OutputLine("Services:");
542 
543     while (GetNextService(&iterator, &config, aLocal) == OT_ERROR_NONE)
544     {
545         OutputService(config);
546     }
547 }
548 
OutputBinary(bool aLocal)549 otError NetworkData::OutputBinary(bool aLocal)
550 {
551     otError error;
552     uint8_t data[255];
553     uint8_t len = sizeof(data);
554 
555     if (aLocal)
556     {
557 #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
558         error = otBorderRouterGetNetData(GetInstancePtr(), false, data, &len);
559 #else
560         error = OT_ERROR_NOT_IMPLEMENTED;
561 #endif
562     }
563     else
564     {
565         error = otNetDataGet(GetInstancePtr(), false, data, &len);
566     }
567     SuccessOrExit(error);
568 
569     OutputBytesLine(data, static_cast<uint8_t>(len));
570 
571 exit:
572     return error;
573 }
574 
575 /**
576  * @cli netdata show
577  * @code
578  * netdata show
579  * Prefixes:
580  * fd00:dead:beef:cafe::/64 paros med dc00
581  * Routes:
582  * fd49:7770:7fc5:0::/64 s med 4000
583  * Services:
584  * 44970 5d c000 s 4000
585  * 44970 01 9a04b000000e10 s 4000
586  * Done
587  * @endcode
588  * @code
589  * netdata show -x
590  * 08040b02174703140040fd00deadbeefcafe0504dc00330007021140
591  * Done
592  * @endcode
593  * @cparam netdata show [@ca{-x}]
594  * *   The optional `-x` argument gets Network Data as hex-encoded TLVs.
595  * @par
596  * `netdata show` from OT CLI gets full Network Data received from the Leader. This command uses several
597  * API functions to combine prefixes, routes, and services, including #otNetDataGetNextOnMeshPrefix,
598  * #otNetDataGetNextRoute, and #otNetDataGetNextService.
599  * @par
600  * @moreinfo{@netdata}.
601  * @csa{br omrprefix}
602  * @csa{br onlinkprefix}
603  * @sa otBorderRouterGetNetData
604  */
Process(Arg aArgs[])605 template <> otError NetworkData::Process<Cmd("show")>(Arg aArgs[])
606 {
607     otError error  = OT_ERROR_INVALID_ARGS;
608     bool    local  = false;
609     bool    binary = false;
610 
611     for (uint8_t i = 0; !aArgs[i].IsEmpty(); i++)
612     {
613         /**
614          * @cli netdata show local
615          * @code
616          * netdata show local
617          * Prefixes:
618          * fd00:dead:beef:cafe::/64 paros med dc00
619          * Routes:
620          * Services:
621          * Done
622          * @endcode
623          * @code
624          * netdata show local -x
625          * 08040b02174703140040fd00deadbeefcafe0504dc00330007021140
626          * Done
627          * @endcode
628          * @cparam netdata show local [@ca{-x}]
629          * *   The optional `-x` argument gets local Network Data as hex-encoded TLVs.
630          * @par
631          * Print local Network Data to sync with the Leader.
632          * @csa{netdata show}
633          */
634         if (aArgs[i] == "local")
635         {
636             local = true;
637         }
638         else if (aArgs[i] == "-x")
639         {
640             binary = true;
641         }
642         else
643         {
644             ExitNow(error = OT_ERROR_INVALID_ARGS);
645         }
646     }
647 
648     if (binary)
649     {
650         error = OutputBinary(local);
651     }
652     else
653     {
654         OutputPrefixes(local);
655         OutputRoutes(local);
656         OutputServices(local);
657         error = OT_ERROR_NONE;
658     }
659 
660 exit:
661     return error;
662 }
663 
Process(Arg aArgs[])664 otError NetworkData::Process(Arg aArgs[])
665 {
666 #define CmdEntry(aCommandString)                                   \
667     {                                                              \
668         aCommandString, &NetworkData::Process<Cmd(aCommandString)> \
669     }
670 
671     static constexpr Command kCommands[] = {
672 #if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE
673         CmdEntry("publish"),
674 #endif
675 #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
676         CmdEntry("register"),
677 #endif
678         CmdEntry("show"),
679         CmdEntry("steeringdata"),
680 #if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE
681         CmdEntry("unpublish"),
682 #endif
683     };
684 
685 #undef CmdEntry
686 
687     static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
688 
689     otError        error = OT_ERROR_INVALID_COMMAND;
690     const Command *command;
691 
692     /**
693      * @cli netdata help
694      * @code
695      * netdata help
696      * help
697      * publish
698      * register
699      * show
700      * steeringdata
701      * unpublish
702      * Done
703      * @endcode
704      * @par
705      * Gets a list of `netdata` CLI commands.
706      * @sa @netdata
707      */
708     if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
709     {
710         OutputCommandTable(kCommands);
711         ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
712     }
713 
714     command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
715     VerifyOrExit(command != nullptr);
716 
717     error = (this->*command->mHandler)(aArgs + 1);
718 
719 exit:
720     return error;
721 }
722 
723 } // namespace Cli
724 } // namespace ot
725