• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2016, 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.
32  */
33 
34 #include "cli_dataset.hpp"
35 
36 #include <stdio.h>
37 #include <stdlib.h>
38 
39 #include <openthread/dataset.h>
40 #include <openthread/dataset_ftd.h>
41 #include <openthread/dataset_updater.h>
42 
43 #include "cli/cli.hpp"
44 
45 namespace ot {
46 namespace Cli {
47 
48 otOperationalDatasetTlvs Dataset::sDatasetTlvs;
49 
LookupMapper(const char * aName) const50 const Dataset::ComponentMapper *Dataset::LookupMapper(const char *aName) const
51 {
52     static constexpr ComponentMapper kMappers[] = {
53         {
54             "activetimestamp",
55             &Components::mIsActiveTimestampPresent,
56             &Dataset::OutputActiveTimestamp,
57             &Dataset::ParseActiveTimestamp,
58         },
59         {
60             "channel",
61             &Components::mIsChannelPresent,
62             &Dataset::OutputChannel,
63             &Dataset::ParseChannel,
64         },
65         {
66             "channelmask",
67             &Components::mIsChannelMaskPresent,
68             &Dataset::OutputChannelMask,
69             &Dataset::ParseChannelMask,
70         },
71         {
72             "delay",
73             &Components::mIsDelayPresent,
74             &Dataset::OutputDelay,
75             &Dataset::ParseDelay,
76         },
77         {
78             "delaytimer", // Alias for "delay "to ensure backward compatibility for "mgmtsetcommand" command
79             &Components::mIsDelayPresent,
80             &Dataset::OutputDelay,
81             &Dataset::ParseDelay,
82         },
83         {
84             "extpanid",
85             &Components::mIsExtendedPanIdPresent,
86             &Dataset::OutputExtendedPanId,
87             &Dataset::ParseExtendedPanId,
88         },
89         {
90             "localprefix", // Alias for "meshlocalprefix" to ensure backward compatibility in "mgmtsetcommand" command
91             &Components::mIsMeshLocalPrefixPresent,
92             &Dataset::OutputMeshLocalPrefix,
93             &Dataset::ParseMeshLocalPrefix,
94         },
95         {
96             "meshlocalprefix",
97             &Components::mIsMeshLocalPrefixPresent,
98             &Dataset::OutputMeshLocalPrefix,
99             &Dataset::ParseMeshLocalPrefix,
100         },
101         {
102             "networkkey",
103             &Components::mIsNetworkKeyPresent,
104             &Dataset::OutputNetworkKey,
105             &Dataset::ParseNetworkKey,
106         },
107         {
108             "networkname",
109             &Components::mIsNetworkNamePresent,
110             &Dataset::OutputNetworkName,
111             &Dataset::ParseNetworkName,
112         },
113         {
114             "panid",
115             &Components::mIsPanIdPresent,
116             &Dataset::OutputPanId,
117             &Dataset::ParsePanId,
118         },
119         {
120             "pendingtimestamp",
121             &Components::mIsPendingTimestampPresent,
122             &Dataset::OutputPendingTimestamp,
123             &Dataset::ParsePendingTimestamp,
124         },
125         {
126             "pskc",
127             &Components::mIsPskcPresent,
128             &Dataset::OutputPskc,
129             &Dataset::ParsePskc,
130         },
131         {
132             "securitypolicy",
133             &Components::mIsSecurityPolicyPresent,
134             &Dataset::OutputSecurityPolicy,
135             &Dataset::ParseSecurityPolicy,
136         },
137         {
138             "wakeupchannel",
139             &Components::mIsWakeupChannelPresent,
140             &Dataset::OutputWakeupChannel,
141             &Dataset::ParseWakeupChannel,
142         },
143     };
144 
145     static_assert(BinarySearch::IsSorted(kMappers), "kMappers is not sorted");
146 
147     return BinarySearch::Find(aName, kMappers);
148 }
149 
150 //---------------------------------------------------------------------------------------------------------------------
151 
152 /**
153  * @cli dataset activetimestamp (get, set)
154  * @code
155  * dataset activetimestamp
156  * 123456789
157  * Done
158  * @endcode
159  * @code
160  * dataset activetimestamp 123456789
161  * Done
162  * @endcode
163  * @cparam dataset activetimestamp [@ca{timestamp}]
164  * Pass the optional `timestamp` argument to set the active timestamp.
165  * @par
166  * Gets or sets #otOperationalDataset::mActiveTimestamp.
167  */
OutputActiveTimestamp(const otOperationalDataset & aDataset)168 void Dataset::OutputActiveTimestamp(const otOperationalDataset &aDataset)
169 {
170     OutputUint64Line(aDataset.mActiveTimestamp.mSeconds);
171 }
172 
173 /**
174  * @cli dataset channel (get,set)
175  * @code
176  * dataset channel
177  * 12
178  * Done
179  * @endcode
180  * @code
181  * dataset channel 12
182  * Done
183  * @endcode
184  * @cparam dataset channel [@ca{channel-num}]
185  * Use the optional `channel-num` argument to set the channel.
186  * @par
187  * Gets or sets #otOperationalDataset::mChannel.
188  */
OutputChannel(const otOperationalDataset & aDataset)189 void Dataset::OutputChannel(const otOperationalDataset &aDataset) { OutputLine("%u", aDataset.mChannel); }
190 
191 /**
192  * @cli dataset wakeupchannel (get,set)
193  * @code
194  * dataset wakeupchannel
195  * 13
196  * Done
197  * @endcode
198  * @code
199  * dataset wakeupchannel 13
200  * Done
201  * @endcode
202  * @cparam dataset wakeupchannel [@ca{channel-num}]
203  * Use the optional `channel-num` argument to set the wake-up channel.
204  * @par
205  * Gets or sets #otOperationalDataset::mWakeupChannel.
206  */
OutputWakeupChannel(const otOperationalDataset & aDataset)207 void Dataset::OutputWakeupChannel(const otOperationalDataset &aDataset) { OutputLine("%u", aDataset.mWakeupChannel); }
208 
209 /**
210  * @cli dataset channelmask (get,set)
211  * @code
212  * dataset channelmask
213  * 0x07fff800
214  * Done
215  * @endcode
216  * @code
217  * dataset channelmask 0x07fff800
218  * Done
219  * @endcode
220  * @cparam dataset channelmask [@ca{channel-mask}]
221  * Use the optional `channel-mask` argument to set the channel mask.
222  * @par
223  * Gets or sets #otOperationalDataset::mChannelMask
224  */
OutputChannelMask(const otOperationalDataset & aDataset)225 void Dataset::OutputChannelMask(const otOperationalDataset &aDataset)
226 {
227     OutputLine("0x%08lx", ToUlong(aDataset.mChannelMask));
228 }
229 
230 /**
231  * @cli dataset delay (get,set)
232  * @code
233  * dataset delay
234  * 1000
235  * Done
236  * @endcode
237  * @code
238  * dataset delay 1000
239  * Done
240  * @endcode
241  * @cparam dataset delay [@ca{delay}]
242  * Use the optional `delay` argument to set the delay timer value.
243  * @par
244  * Gets or sets #otOperationalDataset::mDelay.
245  * @sa otDatasetSetDelayTimerMinimal
246  */
OutputDelay(const otOperationalDataset & aDataset)247 void Dataset::OutputDelay(const otOperationalDataset &aDataset) { OutputLine("%lu", ToUlong(aDataset.mDelay)); }
248 
249 /**
250  * @cli dataset extpanid (get,set)
251  * @code
252  * dataset extpanid
253  * 000db80123456789
254  * Done
255  * @endcode
256  * @code
257  * dataset extpanid 000db80123456789
258  * Done
259  * @endcode
260  * @cparam dataset extpanid [@ca{extpanid}]
261  * Use the optional `extpanid` argument to set the Extended Personal Area Network ID.
262  * @par
263  * Gets or sets #otOperationalDataset::mExtendedPanId.
264  * @note The commissioning credential in the dataset buffer becomes stale after changing
265  * this value. Use `dataset pskc` to reset.
266  * @csa{dataset pskc (get,set)}
267  */
OutputExtendedPanId(const otOperationalDataset & aDataset)268 void Dataset::OutputExtendedPanId(const otOperationalDataset &aDataset) { OutputBytesLine(aDataset.mExtendedPanId.m8); }
269 
270 /**
271  * @cli dataset meshlocalprefix (get,set)
272  * @code
273  * dataset meshlocalprefix
274  * fd00:db8:0:0::/64
275  * Done
276  * @endcode
277  * @code
278  * dataset meshlocalprefix fd00:db8:0:0::
279  * Done
280  * @endcode
281  * @cparam dataset meshlocalprefix [@ca{meshlocalprefix}]
282  * Use the optional `meshlocalprefix` argument to set the Mesh-Local Prefix.
283  * @par
284  * Gets or sets #otOperationalDataset::mMeshLocalPrefix.
285  */
OutputMeshLocalPrefix(const otOperationalDataset & aDataset)286 void Dataset::OutputMeshLocalPrefix(const otOperationalDataset &aDataset)
287 {
288     OutputIp6PrefixLine(aDataset.mMeshLocalPrefix);
289 }
290 
291 /**
292  * @cli dataset networkkey (get,set)
293  * @code
294  * dataset networkkey
295  * 00112233445566778899aabbccddeeff
296  * Done
297  * @endcode
298  * @code
299  * dataset networkkey 00112233445566778899aabbccddeeff
300  * Done
301  * @endcode
302  * @cparam dataset networkkey [@ca{key}]
303  * Use the optional `key` argument to set the Network Key.
304  * @par
305  * Gets or sets #otOperationalDataset::mNetworkKey.
306  */
OutputNetworkKey(const otOperationalDataset & aDataset)307 void Dataset::OutputNetworkKey(const otOperationalDataset &aDataset) { OutputBytesLine(aDataset.mNetworkKey.m8); }
308 
309 /**
310  * @cli dataset networkname (get,set)
311  * @code
312  * dataset networkname
313  * OpenThread
314  * Done
315  * @endcode
316  * @code
317  * dataset networkname OpenThread
318  * Done
319  * @endcode
320  * @cparam dataset networkname [@ca{name}]
321  * Use the optional `name` argument to set the Network Name.
322  * @par
323  * Gets or sets #otOperationalDataset::mNetworkName.
324  * @note The Commissioning Credential in the dataset buffer becomes stale after changing this value.
325  * Use `dataset pskc` to reset.
326  * @csa{dataset pskc (get,set)}
327  */
OutputNetworkName(const otOperationalDataset & aDataset)328 void Dataset::OutputNetworkName(const otOperationalDataset &aDataset) { OutputLine("%s", aDataset.mNetworkName.m8); }
329 
330 /**
331  * @cli dataset panid (get,set)
332  * @code
333  * dataset panid
334  * 0x1234
335  * Done
336  * @endcode
337  * @code
338  * dataset panid 0x1234
339  * Done
340  * @endcode
341  * @cparam dataset panid [@ca{panid}]
342  * Use the optional `panid` argument to set the PAN ID.
343  * @par
344  * Gets or sets #otOperationalDataset::mPanId.
345  */
OutputPanId(const otOperationalDataset & aDataset)346 void Dataset::OutputPanId(const otOperationalDataset &aDataset) { OutputLine("0x%04x", aDataset.mPanId); }
347 
348 /**
349  * @cli dataset pendingtimestamp (get,set)
350  * @code
351  * dataset pendingtimestamp
352  * 123456789
353  * Done
354  * @endcode
355  * @code
356  * dataset pendingtimestamp 123456789
357  * Done
358  * @endcode
359  * @cparam dataset pendingtimestamp [@ca{timestamp}]
360  * Use the optional `timestamp` argument to set the pending timestamp seconds.
361  * @par
362  * Gets or sets #otOperationalDataset::mPendingTimestamp.
363  */
OutputPendingTimestamp(const otOperationalDataset & aDataset)364 void Dataset::OutputPendingTimestamp(const otOperationalDataset &aDataset)
365 {
366     OutputUint64Line(aDataset.mPendingTimestamp.mSeconds);
367 }
368 
369 /**
370  * @cli dataset pskc (get,set)
371  * @code
372  * dataset pskc
373  * 67c0c203aa0b042bfb5381c47aef4d9e
374  * Done
375  * @endcode
376  * @code
377  * dataset pskc -p 123456
378  * Done
379  * @endcode
380  * @code
381  * dataset pskc 67c0c203aa0b042bfb5381c47aef4d9e
382  * Done
383  * @endcode
384  * @cparam dataset pskc [@ca{-p} @ca{passphrase}] | [@ca{key}]
385  * For FTD only, use `-p` with the `passphrase` argument. `-p` generates a pskc from
386  * the UTF-8 encoded `passphrase` that you provide, together with
387  * the network name and extended PAN ID. If set, `-p` uses the dataset buffer;
388  * otherwise, it uses the current stack.
389  * Alternatively, you can set pskc as `key` (hex format).
390  * @par
391  * Gets or sets #otOperationalDataset::mPskc.
392  */
OutputPskc(const otOperationalDataset & aDataset)393 void Dataset::OutputPskc(const otOperationalDataset &aDataset) { OutputBytesLine(aDataset.mPskc.m8); }
394 
395 /**
396  * @cli dataset securitypolicy (get,set)
397  * @code
398  * dataset securitypolicy
399  * 672 onrc
400  * Done
401  * @endcode
402  * @code
403  * dataset securitypolicy 672 onrc
404  * Done
405  * @endcode
406  * @cparam dataset securitypolicy [@ca{rotationtime} [@ca{onrcCepR}]]
407  * *   Use `rotationtime` for `thrKeyRotation`, in units of hours.
408  * *   Security Policy commands use the `onrcCepR` argument mappings to get and set
409  * #otSecurityPolicy members, for example `o` represents
410  * #otSecurityPolicy::mObtainNetworkKeyEnabled.
411  * @moreinfo{@dataset}.
412  * @par
413  * Gets or sets the %Dataset security policy.
414  */
OutputSecurityPolicy(const otOperationalDataset & aDataset)415 void Dataset::OutputSecurityPolicy(const otOperationalDataset &aDataset)
416 {
417     OutputSecurityPolicy(aDataset.mSecurityPolicy);
418 }
419 
420 //---------------------------------------------------------------------------------------------------------------------
421 
ParseActiveTimestamp(Arg * & aArgs,otOperationalDataset & aDataset)422 otError Dataset::ParseActiveTimestamp(Arg *&aArgs, otOperationalDataset &aDataset)
423 {
424     otError error;
425 
426     SuccessOrExit(error = aArgs++->ParseAsUint64(aDataset.mActiveTimestamp.mSeconds));
427     aDataset.mActiveTimestamp.mTicks         = 0;
428     aDataset.mActiveTimestamp.mAuthoritative = false;
429 
430 exit:
431     return error;
432 }
433 
ParseChannel(Arg * & aArgs,otOperationalDataset & aDataset)434 otError Dataset::ParseChannel(Arg *&aArgs, otOperationalDataset &aDataset)
435 {
436     return aArgs++->ParseAsUint16(aDataset.mChannel);
437 }
438 
ParseWakeupChannel(Arg * & aArgs,otOperationalDataset & aDataset)439 otError Dataset::ParseWakeupChannel(Arg *&aArgs, otOperationalDataset &aDataset)
440 {
441     return aArgs++->ParseAsUint16(aDataset.mWakeupChannel);
442 }
443 
ParseChannelMask(Arg * & aArgs,otOperationalDataset & aDataset)444 otError Dataset::ParseChannelMask(Arg *&aArgs, otOperationalDataset &aDataset)
445 {
446     return aArgs++->ParseAsUint32(aDataset.mChannelMask);
447 }
448 
ParseDelay(Arg * & aArgs,otOperationalDataset & aDataset)449 otError Dataset::ParseDelay(Arg *&aArgs, otOperationalDataset &aDataset)
450 {
451     return aArgs++->ParseAsUint32(aDataset.mDelay);
452 }
453 
ParseExtendedPanId(Arg * & aArgs,otOperationalDataset & aDataset)454 otError Dataset::ParseExtendedPanId(Arg *&aArgs, otOperationalDataset &aDataset)
455 {
456     return aArgs++->ParseAsHexString(aDataset.mExtendedPanId.m8);
457 }
458 
ParseMeshLocalPrefix(Arg * & aArgs,otOperationalDataset & aDataset)459 otError Dataset::ParseMeshLocalPrefix(Arg *&aArgs, otOperationalDataset &aDataset)
460 {
461     otError      error;
462     otIp6Address prefix;
463 
464     SuccessOrExit(error = aArgs++->ParseAsIp6Address(prefix));
465 
466     memcpy(aDataset.mMeshLocalPrefix.m8, prefix.mFields.m8, sizeof(aDataset.mMeshLocalPrefix.m8));
467 
468 exit:
469     return error;
470 }
471 
ParseNetworkKey(Arg * & aArgs,otOperationalDataset & aDataset)472 otError Dataset::ParseNetworkKey(Arg *&aArgs, otOperationalDataset &aDataset)
473 {
474     return aArgs++->ParseAsHexString(aDataset.mNetworkKey.m8);
475 }
476 
ParseNetworkName(Arg * & aArgs,otOperationalDataset & aDataset)477 otError Dataset::ParseNetworkName(Arg *&aArgs, otOperationalDataset &aDataset)
478 {
479     otError error = OT_ERROR_NONE;
480 
481     VerifyOrExit(!aArgs->IsEmpty(), error = OT_ERROR_INVALID_ARGS);
482     error = otNetworkNameFromString(&aDataset.mNetworkName, aArgs++->GetCString());
483 
484 exit:
485     return error;
486 }
487 
ParsePanId(Arg * & aArgs,otOperationalDataset & aDataset)488 otError Dataset::ParsePanId(Arg *&aArgs, otOperationalDataset &aDataset)
489 {
490     return aArgs++->ParseAsUint16(aDataset.mPanId);
491 }
492 
ParsePendingTimestamp(Arg * & aArgs,otOperationalDataset & aDataset)493 otError Dataset::ParsePendingTimestamp(Arg *&aArgs, otOperationalDataset &aDataset)
494 {
495     otError error;
496 
497     SuccessOrExit(error = aArgs++->ParseAsUint64(aDataset.mPendingTimestamp.mSeconds));
498     aDataset.mPendingTimestamp.mTicks         = 0;
499     aDataset.mPendingTimestamp.mAuthoritative = false;
500 
501 exit:
502     return error;
503 }
504 
ParsePskc(Arg * & aArgs,otOperationalDataset & aDataset)505 otError Dataset::ParsePskc(Arg *&aArgs, otOperationalDataset &aDataset)
506 {
507     otError error;
508 
509 #if OPENTHREAD_FTD
510     if (*aArgs == "-p")
511     {
512         aArgs++;
513         VerifyOrExit(!aArgs->IsEmpty(), error = OT_ERROR_INVALID_ARGS);
514 
515         SuccessOrExit(error = otDatasetGeneratePskc(
516                           aArgs->GetCString(),
517                           (aDataset.mComponents.mIsNetworkNamePresent
518                                ? &aDataset.mNetworkName
519                                : reinterpret_cast<const otNetworkName *>(otThreadGetNetworkName(GetInstancePtr()))),
520                           (aDataset.mComponents.mIsExtendedPanIdPresent ? &aDataset.mExtendedPanId
521                                                                         : otThreadGetExtendedPanId(GetInstancePtr())),
522                           &aDataset.mPskc));
523         aArgs++;
524     }
525     else
526 #endif
527     {
528         ExitNow(error = aArgs++->ParseAsHexString(aDataset.mPskc.m8));
529     }
530 
531 exit:
532     return error;
533 }
534 
ParseSecurityPolicy(Arg * & aArgs,otOperationalDataset & aDataset)535 otError Dataset::ParseSecurityPolicy(Arg *&aArgs, otOperationalDataset &aDataset)
536 {
537     return ParseSecurityPolicy(aDataset.mSecurityPolicy, aArgs);
538 }
539 
ParseTlvs(Arg & aArg,otOperationalDatasetTlvs & aDatasetTlvs)540 otError Dataset::ParseTlvs(Arg &aArg, otOperationalDatasetTlvs &aDatasetTlvs)
541 {
542     otError  error;
543     uint16_t length;
544 
545     length = sizeof(aDatasetTlvs.mTlvs);
546     SuccessOrExit(error = aArg.ParseAsHexString(length, aDatasetTlvs.mTlvs));
547     aDatasetTlvs.mLength = static_cast<uint8_t>(length);
548 
549 exit:
550     return error;
551 }
552 
553 //---------------------------------------------------------------------------------------------------------------------
554 
ProcessCommand(const ComponentMapper & aMapper,Arg aArgs[])555 otError Dataset::ProcessCommand(const ComponentMapper &aMapper, Arg aArgs[])
556 {
557     otError              error = OT_ERROR_NONE;
558     otOperationalDataset dataset;
559 
560     if (aArgs[0].IsEmpty())
561     {
562         SuccessOrExit(error = otDatasetParseTlvs(&sDatasetTlvs, &dataset));
563 
564         if (dataset.mComponents.*aMapper.mIsPresentPtr)
565         {
566             (this->*aMapper.mOutput)(dataset);
567         }
568     }
569     else
570     {
571         ClearAllBytes(dataset);
572         SuccessOrExit(error = (this->*aMapper.mParse)(aArgs, dataset));
573         dataset.mComponents.*aMapper.mIsPresentPtr = true;
574         SuccessOrExit(error = otDatasetUpdateTlvs(&dataset, &sDatasetTlvs));
575     }
576 
577 exit:
578     return error;
579 }
580 
Print(otOperationalDatasetTlvs & aDatasetTlvs)581 otError Dataset::Print(otOperationalDatasetTlvs &aDatasetTlvs)
582 {
583     struct ComponentTitle
584     {
585         const char *mTitle; // Title to output.
586         const char *mName;  // To use with `LookupMapper()`.
587     };
588 
589     static const ComponentTitle kTitles[] = {
590         {"Pending Timestamp", "pendingtimestamp"},
591         {"Active Timestamp", "activetimestamp"},
592         {"Channel", "channel"},
593         {"Wake-up Channel", "wakeupchannel"},
594         {"Channel Mask", "channelmask"},
595         {"Delay", "delay"},
596         {"Ext PAN ID", "extpanid"},
597         {"Mesh Local Prefix", "meshlocalprefix"},
598         {"Network Key", "networkkey"},
599         {"Network Name", "networkname"},
600         {"PAN ID", "panid"},
601         {"PSKc", "pskc"},
602         {"Security Policy", "securitypolicy"},
603     };
604 
605     otError              error;
606     otOperationalDataset dataset;
607 
608     SuccessOrExit(error = otDatasetParseTlvs(&aDatasetTlvs, &dataset));
609 
610     for (const ComponentTitle &title : kTitles)
611     {
612         const ComponentMapper *mapper = LookupMapper(title.mName);
613 
614         if (dataset.mComponents.*mapper->mIsPresentPtr)
615         {
616             OutputFormat("%s: ", title.mTitle);
617             (this->*mapper->mOutput)(dataset);
618         }
619     }
620 
621 exit:
622     return error;
623 }
624 
625 /**
626  * @cli dataset init (active,new,pending,tlvs)
627  * @code
628  * dataset init new
629  * Done
630  * @endcode
631  * @cparam dataset init {@ca{active}|@ca{new}|@ca{pending}|@ca{tlvs}} [@ca{hex-encoded-tlvs}]
632  * Use `new` to initialize a new dataset, then enter the command `dataset commit active`.
633  * Use `tlvs` for hex-encoded TLVs.
634  * @par
635  * OT CLI checks for `active`, `pending`, or `tlvs` and returns the corresponding values. Otherwise,
636  * OT CLI creates a new, random network and returns a new dataset.
637  * @csa{dataset commit active}
638  * @csa{dataset active}
639  */
Process(Arg aArgs[])640 template <> otError Dataset::Process<Cmd("init")>(Arg aArgs[])
641 {
642     otError error = OT_ERROR_INVALID_ARGS;
643 
644     if (aArgs[0] == "active")
645     {
646         error = otDatasetGetActiveTlvs(GetInstancePtr(), &sDatasetTlvs);
647     }
648     else if (aArgs[0] == "pending")
649     {
650         error = otDatasetGetPendingTlvs(GetInstancePtr(), &sDatasetTlvs);
651     }
652 #if OPENTHREAD_FTD
653     else if (aArgs[0] == "new")
654     {
655         otOperationalDataset dataset;
656 
657         SuccessOrExit(error = otDatasetCreateNewNetwork(GetInstancePtr(), &dataset));
658         otDatasetConvertToTlvs(&dataset, &sDatasetTlvs);
659     }
660 #endif
661     else if (aArgs[0] == "tlvs")
662     {
663         ExitNow(error = ParseTlvs(aArgs[1], sDatasetTlvs));
664     }
665 
666 exit:
667     return error;
668 }
669 
670 /**
671  * @cli dataset active
672  * @code
673  * dataset active
674  * Active Timestamp: 1
675  * Channel: 13
676  * Channel Mask: 0x07fff800
677  * Ext PAN ID: d63e8e3e495ebbc3
678  * Mesh Local Prefix: fd3d:b50b:f96d:722d::/64
679  * Network Key: dfd34f0f05cad978ec4e32b0413038ff
680  * Network Name: OpenThread-8f28
681  * PAN ID: 0x8f28
682  * PSKc: c23a76e98f1a6483639b1ac1271e2e27
683  * Security Policy: 0, onrcb
684  * Done
685  * @endcode
686  * @code
687  * dataset active -x
688  * 0e08000000000001000000030000103506000...3023d82c841eff0e68db86f35740c030000ff
689  * Done
690  * @endcode
691  * @cparam dataset active [-x]
692  * The optional `-x` argument prints the Active Operational %Dataset values as hex-encoded TLVs.
693  * @par api_copy
694  * #otDatasetGetActive
695  * @par
696  * OT CLI uses #otOperationalDataset members to return dataset values to the console.
697  */
Process(Arg aArgs[])698 template <> otError Dataset::Process<Cmd("active")>(Arg aArgs[])
699 {
700     otError                  error;
701     otOperationalDatasetTlvs dataset;
702 
703     SuccessOrExit(error = otDatasetGetActiveTlvs(GetInstancePtr(), &dataset));
704 
705     if (aArgs[0].IsEmpty())
706     {
707         error = Print(dataset);
708     }
709     else if (aArgs[0] == "-x")
710     {
711         OutputBytesLine(dataset.mTlvs, dataset.mLength);
712     }
713     else
714     {
715         error = OT_ERROR_INVALID_ARGS;
716     }
717 
718 exit:
719     return error;
720 }
721 
Process(Arg aArgs[])722 template <> otError Dataset::Process<Cmd("pending")>(Arg aArgs[])
723 {
724     otError                  error;
725     otOperationalDatasetTlvs datasetTlvs;
726 
727     SuccessOrExit(error = otDatasetGetPendingTlvs(GetInstancePtr(), &datasetTlvs));
728 
729     if (aArgs[0].IsEmpty())
730     {
731         error = Print(datasetTlvs);
732     }
733     else if (aArgs[0] == "-x")
734     {
735         OutputBytesLine(datasetTlvs.mTlvs, datasetTlvs.mLength);
736     }
737     else
738     {
739         error = OT_ERROR_INVALID_ARGS;
740     }
741 
742 exit:
743     return error;
744 }
745 
746 /**
747  * @cli dataset clear
748  * @code
749  * dataset clear
750  * Done
751  * @endcode
752  * @par
753  * Reset the Operational %Dataset buffer.
754  */
Process(Arg aArgs[])755 template <> otError Dataset::Process<Cmd("clear")>(Arg aArgs[])
756 {
757     OT_UNUSED_VARIABLE(aArgs);
758 
759     ClearAllBytes(sDatasetTlvs);
760     return OT_ERROR_NONE;
761 }
762 
Process(Arg aArgs[])763 template <> otError Dataset::Process<Cmd("commit")>(Arg aArgs[])
764 {
765     otError error = OT_ERROR_INVALID_ARGS;
766 
767     /**
768      * @cli dataset commit active
769      * @code
770      * dataset commit active
771      * Done
772      * @endcode
773      * @par
774      * Commit the Operational %Dataset buffer to Active Operational %Dataset.
775      * @csa{dataset commit pending}
776      * @sa #otDatasetSetPending
777      */
778     if (aArgs[0] == "active")
779     {
780         error = otDatasetSetActiveTlvs(GetInstancePtr(), &sDatasetTlvs);
781     }
782     /**
783      * @cli dataset commit pending
784      * @code
785      * dataset commit pending
786      * Done
787      * @endcode
788      * @par
789      * Commit the Operational %Dataset buffer to Pending Operational %Dataset.
790      * @csa{dataset commit active}
791      * @sa #otDatasetSetActive
792      */
793     else if (aArgs[0] == "pending")
794     {
795         error = otDatasetSetPendingTlvs(GetInstancePtr(), &sDatasetTlvs);
796     }
797 
798     return error;
799 }
800 
Process(Arg aArgs[])801 template <> otError Dataset::Process<Cmd("mgmtsetcommand")>(Arg aArgs[])
802 {
803     otError                  error = OT_ERROR_NONE;
804     otOperationalDataset     dataset;
805     otOperationalDatasetTlvs tlvs;
806 
807     ClearAllBytes(dataset);
808     ClearAllBytes(tlvs);
809 
810     for (Arg *arg = &aArgs[1]; !arg->IsEmpty();)
811     {
812         const ComponentMapper *mapper = LookupMapper(arg->GetCString());
813 
814         if (mapper != nullptr)
815         {
816             arg++;
817             SuccessOrExit(error = (this->*mapper->mParse)(arg, dataset));
818             dataset.mComponents.*mapper->mIsPresentPtr = true;
819         }
820         else if (*arg == "-x")
821         {
822             arg++;
823             SuccessOrExit(error = ParseTlvs(*arg, tlvs));
824             arg++;
825         }
826         else
827         {
828             ExitNow(error = OT_ERROR_INVALID_ARGS);
829         }
830     }
831 
832     /**
833      * @cli dataset mgmtsetcommand active
834      * @code
835      * dataset mgmtsetcommand active activetimestamp 123 securitypolicy 1 onrcb
836      * Done
837      * @endcode
838      * @cparam dataset mgmtsetcommand active [@ca{dataset-components}] [-x @ca{tlv-list}]
839      * To learn more about these parameters and argument mappings, refer to @dataset.
840      * @par
841      * @note This command is primarily used for testing only.
842      * @par api_copy
843      * #otDatasetSendMgmtActiveSet
844      * @csa{dataset mgmtgetcommand active}
845      * @csa{dataset mgmtgetcommand pending}
846      * @csa{dataset mgmtsetcommand pending}
847      */
848     if (aArgs[0] == "active")
849     {
850         error =
851             otDatasetSendMgmtActiveSet(GetInstancePtr(), &dataset, tlvs.mTlvs, tlvs.mLength, /* aCallback */ nullptr,
852                                        /* aContext */ nullptr);
853     }
854     /**
855      * @cli dataset mgmtsetcommand pending
856      * @code
857      * dataset mgmtsetcommand pending activetimestamp 123 securitypolicy 1 onrcb
858      * Done
859      * @endcode
860      * @cparam dataset mgmtsetcommand pending [@ca{dataset-components}] [-x @ca{tlv-list}]
861      * To learn more about these parameters and argument mappings, refer to @dataset.
862      * @par
863      * @note This command is primarily used for testing only.
864      * @par api_copy
865      * #otDatasetSendMgmtPendingSet
866      * @csa{dataset mgmtgetcommand active}
867      * @csa{dataset mgmtgetcommand pending}
868      * @csa{dataset mgmtsetcommand active}
869      */
870     else if (aArgs[0] == "pending")
871     {
872         error =
873             otDatasetSendMgmtPendingSet(GetInstancePtr(), &dataset, tlvs.mTlvs, tlvs.mLength, /* aCallback */ nullptr,
874                                         /* aContext */ nullptr);
875     }
876     else
877     {
878         error = OT_ERROR_INVALID_ARGS;
879     }
880 
881 exit:
882     return error;
883 }
884 
Process(Arg aArgs[])885 template <> otError Dataset::Process<Cmd("mgmtgetcommand")>(Arg aArgs[])
886 {
887     otError                        error = OT_ERROR_NONE;
888     otOperationalDatasetComponents datasetComponents;
889     otOperationalDatasetTlvs       tlvs;
890     bool                           destAddrSpecified = false;
891     otIp6Address                   address;
892 
893     ClearAllBytes(datasetComponents);
894     ClearAllBytes(tlvs);
895 
896     for (Arg *arg = &aArgs[1]; !arg->IsEmpty(); arg++)
897     {
898         const ComponentMapper *mapper = LookupMapper(arg->GetCString());
899 
900         if (mapper != nullptr)
901         {
902             datasetComponents.*mapper->mIsPresentPtr = true;
903         }
904         else if (*arg == "-x")
905         {
906             arg++;
907             SuccessOrExit(error = ParseTlvs(*arg, tlvs));
908         }
909         else if (*arg == "address")
910         {
911             arg++;
912             SuccessOrExit(error = arg->ParseAsIp6Address(address));
913             destAddrSpecified = true;
914         }
915         else
916         {
917             ExitNow(error = OT_ERROR_INVALID_ARGS);
918         }
919     }
920 
921     /**
922      * @cli dataset mgmtgetcommand active
923      * @code
924      * dataset mgmtgetcommand active address fdde:ad00:beef:0:558:f56b:d688:799 activetimestamp securitypolicy
925      * Done
926      * @endcode
927      * @code
928      * dataset mgmtgetcommand active networkname
929      * Done
930      * @endcode
931      * @cparam dataset mgmtgetcommand active [address @ca{leader-address}] [@ca{dataset-components}] [-x @ca{tlv-list}]
932      * *    Use `address` to specify the IPv6 destination; otherwise, the Leader ALOC is used as default.
933      * *    For `dataset-components`, you can pass any combination of #otOperationalDatasetComponents, for
934      *      example `activetimestamp`, `pendingtimestamp`, or `networkkey`.
935      * *    The optional `-x` argument specifies raw TLVs to be requested.
936      * @par
937      * OT CLI sends a MGMT_ACTIVE_GET with the relevant arguments.
938      * To learn more about these parameters and argument mappings, refer to @dataset.
939      * @note This command is primarily used for testing only.
940      * @par api_copy
941      * #otDatasetSendMgmtActiveGet
942      * @csa{dataset mgmtgetcommand pending}
943      * @csa{dataset mgmtsetcommand active}
944      * @csa{dataset mgmtsetcommand pending}
945      */
946     if (aArgs[0] == "active")
947     {
948         error = otDatasetSendMgmtActiveGet(GetInstancePtr(), &datasetComponents, tlvs.mTlvs, tlvs.mLength,
949                                            destAddrSpecified ? &address : nullptr);
950     }
951     /**
952      * @cli dataset mgmtgetcommand pending
953      * @code
954      * dataset mgmtgetcommand pending address fdde:ad00:beef:0:558:f56b:d688:799 activetimestamp securitypolicy
955      * Done
956      * @endcode
957      * @code
958      * dataset mgmtgetcommand pending networkname
959      * Done
960      * @endcode
961      * @cparam dataset mgmtgetcommand pending [address @ca{leader-address}] [@ca{dataset-components}] [-x @ca{tlv-list}]
962      * To learn more about these parameters and argument mappings, refer to @dataset.
963      * @par
964      * @note This command is primarily used for testing only.
965      * @par api_copy
966      * #otDatasetSendMgmtPendingGet
967      * @csa{dataset mgmtgetcommand active}
968      * @csa{dataset mgmtsetcommand active}
969      * @csa{dataset mgmtsetcommand pending}
970      */
971     else if (aArgs[0] == "pending")
972     {
973         error = otDatasetSendMgmtPendingGet(GetInstancePtr(), &datasetComponents, tlvs.mTlvs, tlvs.mLength,
974                                             destAddrSpecified ? &address : nullptr);
975     }
976     else
977     {
978         error = OT_ERROR_INVALID_ARGS;
979     }
980 
981 exit:
982     return error;
983 }
984 
OutputSecurityPolicy(const otSecurityPolicy & aSecurityPolicy)985 void Dataset::OutputSecurityPolicy(const otSecurityPolicy &aSecurityPolicy)
986 {
987     OutputFormat("%u ", aSecurityPolicy.mRotationTime);
988 
989     if (aSecurityPolicy.mObtainNetworkKeyEnabled)
990     {
991         OutputFormat("o");
992     }
993 
994     if (aSecurityPolicy.mNativeCommissioningEnabled)
995     {
996         OutputFormat("n");
997     }
998 
999     if (aSecurityPolicy.mRoutersEnabled)
1000     {
1001         OutputFormat("r");
1002     }
1003 
1004     if (aSecurityPolicy.mExternalCommissioningEnabled)
1005     {
1006         OutputFormat("c");
1007     }
1008 
1009     if (aSecurityPolicy.mCommercialCommissioningEnabled)
1010     {
1011         OutputFormat("C");
1012     }
1013 
1014     if (aSecurityPolicy.mAutonomousEnrollmentEnabled)
1015     {
1016         OutputFormat("e");
1017     }
1018 
1019     if (aSecurityPolicy.mNetworkKeyProvisioningEnabled)
1020     {
1021         OutputFormat("p");
1022     }
1023 
1024     if (aSecurityPolicy.mNonCcmRoutersEnabled)
1025     {
1026         OutputFormat("R");
1027     }
1028 
1029     OutputLine(" %u", aSecurityPolicy.mVersionThresholdForRouting);
1030 }
1031 
ParseSecurityPolicy(otSecurityPolicy & aSecurityPolicy,Arg * & aArgs)1032 otError Dataset::ParseSecurityPolicy(otSecurityPolicy &aSecurityPolicy, Arg *&aArgs)
1033 {
1034     static constexpr uint8_t kMaxVersionThreshold = 7;
1035 
1036     otError          error;
1037     otSecurityPolicy policy;
1038     uint8_t          versionThreshold;
1039 
1040     ClearAllBytes(policy);
1041 
1042     SuccessOrExit(error = aArgs->ParseAsUint16(policy.mRotationTime));
1043     aArgs++;
1044 
1045     VerifyOrExit(!aArgs->IsEmpty());
1046 
1047     for (const char *flag = aArgs->GetCString(); *flag != '\0'; flag++)
1048     {
1049         switch (*flag)
1050         {
1051         case 'o':
1052             policy.mObtainNetworkKeyEnabled = true;
1053             break;
1054 
1055         case 'n':
1056             policy.mNativeCommissioningEnabled = true;
1057             break;
1058 
1059         case 'r':
1060             policy.mRoutersEnabled = true;
1061             break;
1062 
1063         case 'c':
1064             policy.mExternalCommissioningEnabled = true;
1065             break;
1066 
1067         case 'C':
1068             policy.mCommercialCommissioningEnabled = true;
1069             break;
1070 
1071         case 'e':
1072             policy.mAutonomousEnrollmentEnabled = true;
1073             break;
1074 
1075         case 'p':
1076             policy.mNetworkKeyProvisioningEnabled = true;
1077             break;
1078 
1079         case 'R':
1080             policy.mNonCcmRoutersEnabled = true;
1081             break;
1082 
1083         default:
1084             ExitNow(error = OT_ERROR_INVALID_ARGS);
1085         }
1086     }
1087 
1088     aArgs++;
1089     VerifyOrExit(!aArgs->IsEmpty());
1090 
1091     SuccessOrExit(error = aArgs->ParseAsUint8(versionThreshold));
1092     aArgs++;
1093     VerifyOrExit(versionThreshold <= kMaxVersionThreshold, error = OT_ERROR_INVALID_ARGS);
1094     policy.mVersionThresholdForRouting = versionThreshold;
1095 
1096 exit:
1097     if (error == OT_ERROR_NONE)
1098     {
1099         aSecurityPolicy = policy;
1100     }
1101 
1102     return error;
1103 }
1104 
1105 /**
1106  * @cli dataset set (active,pending)
1107  * @code
1108  * dataset set active 0e08000000000001000000030000103506000...3023d82c841eff0e68db86f35740c030000ff
1109  * Done
1110  * @endcode
1111  * @code
1112  * dataset set pending 0e08000000000001000000030000103506000...3023d82c841eff0e68db86f35740c030000ff
1113  * Done
1114  * @endcode
1115  * @cparam dataset set {active|pending} @ca{tlvs}
1116  * @par
1117  * The CLI `dataset set` command sets the Active Operational %Dataset using hex-encoded TLVs.
1118  * @par api_copy
1119  * #otDatasetSetActive
1120  */
Process(Arg aArgs[])1121 template <> otError Dataset::Process<Cmd("set")>(Arg aArgs[])
1122 {
1123     otError                  error = OT_ERROR_NONE;
1124     otOperationalDatasetTlvs datasetTlvs;
1125 
1126     SuccessOrExit(error = ParseTlvs(aArgs[1], datasetTlvs));
1127 
1128     if (aArgs[0] == "active")
1129     {
1130         error = otDatasetSetActiveTlvs(GetInstancePtr(), &datasetTlvs);
1131     }
1132     else if (aArgs[0] == "pending")
1133     {
1134         error = otDatasetSetPendingTlvs(GetInstancePtr(), &datasetTlvs);
1135     }
1136     else
1137     {
1138         error = OT_ERROR_INVALID_ARGS;
1139     }
1140 
1141 exit:
1142     return error;
1143 }
1144 
1145 /**
1146  * @cli dataset tlvs
1147  * @code
1148  * dataset tlvs
1149  * 0e080000000000010000000300001635060004001fffe0020...f7f8
1150  * Done
1151  * @endcode
1152  * @par api_copy
1153  * #otDatasetConvertToTlvs
1154  */
Process(Arg aArgs[])1155 template <> otError Dataset::Process<Cmd("tlvs")>(Arg aArgs[])
1156 {
1157     otError error = OT_ERROR_NONE;
1158 
1159     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
1160     OutputBytesLine(sDatasetTlvs.mTlvs, sDatasetTlvs.mLength);
1161 
1162 exit:
1163     return error;
1164 }
1165 
1166 #if OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD
1167 
Process(Arg aArgs[])1168 template <> otError Dataset::Process<Cmd("updater")>(Arg aArgs[])
1169 {
1170     otError error = OT_ERROR_NONE;
1171 
1172     /**
1173      * @cli dataset updater
1174      * @code
1175      * dataset updater
1176      * Enabled
1177      * Done
1178      * @endcode
1179      * @par api_copy
1180      * #otDatasetUpdaterIsUpdateOngoing
1181      */
1182     if (aArgs[0].IsEmpty())
1183     {
1184         OutputEnabledDisabledStatus(otDatasetUpdaterIsUpdateOngoing(GetInstancePtr()));
1185     }
1186     /**
1187      * @cli dataset updater start
1188      * @code
1189      * channel
1190      * 19
1191      * Done
1192      * dataset clear
1193      * Done
1194      * dataset channel 15
1195      * Done
1196      * dataset
1197      * Channel: 15
1198      * Done
1199      * dataset updater start
1200      * Done
1201      * dataset updater
1202      * Enabled
1203      * Done
1204      * Dataset update complete: OK
1205      * channel
1206      * 15
1207      * Done
1208      * @endcode
1209      * @par api_copy
1210      * #otDatasetUpdaterRequestUpdate
1211      */
1212     else if (aArgs[0] == "start")
1213     {
1214         otOperationalDataset dataset;
1215 
1216         SuccessOrExit(error = otDatasetParseTlvs(&sDatasetTlvs, &dataset));
1217         SuccessOrExit(
1218             error = otDatasetUpdaterRequestUpdate(GetInstancePtr(), &dataset, &Dataset::HandleDatasetUpdater, this));
1219     }
1220     /**
1221      * @cli dataset updater cancel
1222      * @code
1223      * @dataset updater cancel
1224      * Done
1225      * @endcode
1226      * @par api_copy
1227      * #otDatasetUpdaterCancelUpdate
1228      */
1229     else if (aArgs[0] == "cancel")
1230     {
1231         otDatasetUpdaterCancelUpdate(GetInstancePtr());
1232     }
1233     else
1234     {
1235         error = OT_ERROR_INVALID_ARGS;
1236     }
1237 
1238 exit:
1239     return error;
1240 }
1241 
HandleDatasetUpdater(otError aError,void * aContext)1242 void Dataset::HandleDatasetUpdater(otError aError, void *aContext)
1243 {
1244     static_cast<Dataset *>(aContext)->HandleDatasetUpdater(aError);
1245 }
1246 
HandleDatasetUpdater(otError aError)1247 void Dataset::HandleDatasetUpdater(otError aError)
1248 {
1249     OutputLine("Dataset update complete: %s", otThreadErrorToString(aError));
1250 }
1251 
1252 #endif // OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD
1253 
Process(Arg aArgs[])1254 otError Dataset::Process(Arg aArgs[])
1255 {
1256 #define CmdEntry(aCommandString)                               \
1257     {                                                          \
1258         aCommandString, &Dataset::Process<Cmd(aCommandString)> \
1259     }
1260 
1261     static constexpr Command kCommands[] = {
1262         CmdEntry("active"),
1263         CmdEntry("clear"),
1264         CmdEntry("commit"),
1265         CmdEntry("init"),
1266         CmdEntry("mgmtgetcommand"),
1267         CmdEntry("mgmtsetcommand"),
1268         CmdEntry("pending"),
1269         CmdEntry("set"),
1270         CmdEntry("tlvs"),
1271 #if OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD
1272         CmdEntry("updater"),
1273 #endif
1274     };
1275 
1276 #undef CmdEntry
1277 
1278     static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
1279 
1280     otError                error = OT_ERROR_INVALID_COMMAND;
1281     const Command         *command;
1282     const ComponentMapper *mapper;
1283 
1284     if (aArgs[0].IsEmpty())
1285     {
1286         ExitNow(error = Print(sDatasetTlvs));
1287     }
1288 
1289     /**
1290      * @cli dataset help
1291      * @code
1292      * dataset help
1293      * help
1294      * active
1295      * activetimestamp
1296      * channel
1297      * channelmask
1298      * clear
1299      * commit
1300      * delay
1301      * extpanid
1302      * init
1303      * meshlocalprefix
1304      * mgmtgetcommand
1305      * mgmtsetcommand
1306      * networkkey
1307      * networkname
1308      * panid
1309      * pending
1310      * pendingtimestamp
1311      * pskc
1312      * securitypolicy
1313      * set
1314      * tlvs
1315      * Done
1316      * @endcode
1317      * @par
1318      * Gets a list of `dataset` CLI commands. @moreinfo{@dataset}.
1319      */
1320     if (aArgs[0] == "help")
1321     {
1322         OutputCommandTable(kCommands);
1323         ExitNow(error = OT_ERROR_NONE);
1324     }
1325 
1326     mapper = LookupMapper(aArgs[0].GetCString());
1327 
1328     if (mapper != nullptr)
1329     {
1330         error = ProcessCommand(*mapper, aArgs + 1);
1331         ExitNow();
1332     }
1333 
1334     command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
1335     VerifyOrExit(command != nullptr);
1336 
1337     error = (this->*command->mHandler)(aArgs + 1);
1338 
1339 exit:
1340     return error;
1341 }
1342 
1343 } // namespace Cli
1344 } // namespace ot
1345