1 /*
2  *  Copyright (c) 2019, 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 a simple CLI for the Commissioner role.
32  */
33 
34 #include "cli_commissioner.hpp"
35 
36 #include "cli/cli.hpp"
37 
38 #if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD
39 
40 namespace ot {
41 namespace Cli {
42 
43 /**
44  * @cli commissioner announce
45  * @code
46  * commissioner announce 0x00050000 2 32 fdde:ad00:beef:0:0:ff:fe00:c00
47  * Done
48  * @endcode
49  * @cparam commissioner announce @ca{mask} @ca{count} @ca{period} @ca{destination}
50  *   * `mask`: Bitmask that identifies channels for sending MLE `Announce` messages.
51  *   * `count`: Number of MLE `Announce` transmissions per channel.
52  *   * `period`: Number of milliseconds between successive MLE `Announce` transmissions.
53  *   * `destination`: Destination IPv6 address for the message. The message may be multicast.
54  * @par
55  * Sends an Announce Begin message.
56  * @note Use this command only after successfully starting the %Commissioner role
57  * with the `commissioner start` command.
58  * @csa{commissioner start}
59  * @sa otCommissionerAnnounceBegin
60  */
Process(Arg aArgs[])61 template <> otError Commissioner::Process<Cmd("announce")>(Arg aArgs[])
62 {
63     otError      error;
64     uint32_t     mask;
65     uint8_t      count;
66     uint16_t     period;
67     otIp6Address address;
68 
69     SuccessOrExit(error = aArgs[0].ParseAsUint32(mask));
70     SuccessOrExit(error = aArgs[1].ParseAsUint8(count));
71     SuccessOrExit(error = aArgs[2].ParseAsUint16(period));
72     SuccessOrExit(error = aArgs[3].ParseAsIp6Address(address));
73 
74     error = otCommissionerAnnounceBegin(GetInstancePtr(), mask, count, period, &address);
75 
76 exit:
77     return error;
78 }
79 
80 /**
81  * @cli commissioner energy
82  * @code
83  * commissioner energy 0x00050000 2 32 1000 fdde:ad00:beef:0:0:ff:fe00:c00
84  * Done
85  * Energy: 00050000 0 0 0 0
86  * @endcode
87  * @cparam commissioner energy @ca{mask} @ca{count} @ca{period} @ca{scanDuration} @ca{destination}
88  *   * `mask`: Bitmask that identifies channels for performing IEEE 802.15.4 energy scans.
89  *   * `count`: Number of IEEE 802.15.4 energy scans per channel.
90  *   * `period`: Number of milliseconds between successive IEEE 802.15.4 energy scans.
91  *   * `scanDuration`: Scan duration in milliseconds to use when
92  *     performing an IEEE 802.15.4 energy scan.
93  *   * `destination`: Destination IPv6 address for the message. The message may be multicast.
94  * @par
95  * Sends an Energy Scan Query message. Command output is printed as it is received.
96  * @note Use this command only after successfully starting the %Commissioner role
97  * with the `commissioner start` command.
98  * @csa{commissioner start}
99  * @sa otCommissionerEnergyScan
100  */
Process(Arg aArgs[])101 template <> otError Commissioner::Process<Cmd("energy")>(Arg aArgs[])
102 {
103     otError      error;
104     uint32_t     mask;
105     uint8_t      count;
106     uint16_t     period;
107     uint16_t     scanDuration;
108     otIp6Address address;
109 
110     SuccessOrExit(error = aArgs[0].ParseAsUint32(mask));
111     SuccessOrExit(error = aArgs[1].ParseAsUint8(count));
112     SuccessOrExit(error = aArgs[2].ParseAsUint16(period));
113     SuccessOrExit(error = aArgs[3].ParseAsUint16(scanDuration));
114     SuccessOrExit(error = aArgs[4].ParseAsIp6Address(address));
115 
116     error = otCommissionerEnergyScan(GetInstancePtr(), mask, count, period, scanDuration, &address,
117                                      &Commissioner::HandleEnergyReport, this);
118 
119 exit:
120     return error;
121 }
122 
Process(Arg aArgs[])123 template <> otError Commissioner::Process<Cmd("joiner")>(Arg aArgs[])
124 {
125     otError             error = OT_ERROR_NONE;
126     otExtAddress        addr;
127     const otExtAddress *addrPtr = nullptr;
128     otJoinerDiscerner   discerner;
129 
130     /**
131      * @cli commissioner joiner table
132      * @code
133      * commissioner joiner table
134      * | ID                    | PSKd                             | Expiration |
135      * +-----------------------+----------------------------------+------------+
136      * |                     * |                           J01NME |      81015 |
137      * |      d45e64fa83f81cf7 |                           J01NME |     101204 |
138      * | 0x0000000000000abc/12 |                           J01NME |     114360 |
139      * Done
140      * @endcode
141      * @par
142      * Lists all %Joiner entries in table format.
143      */
144     if (aArgs[0] == "table")
145     {
146         uint16_t     iter = 0;
147         otJoinerInfo joinerInfo;
148 
149         static const char *const kJoinerTableTitles[] = {"ID", "PSKd", "Expiration"};
150 
151         static const uint8_t kJoinerTableColumnWidths[] = {
152             23,
153             34,
154             12,
155         };
156 
157         OutputTableHeader(kJoinerTableTitles, kJoinerTableColumnWidths);
158 
159         while (otCommissionerGetNextJoinerInfo(GetInstancePtr(), &iter, &joinerInfo) == OT_ERROR_NONE)
160         {
161             switch (joinerInfo.mType)
162             {
163             case OT_JOINER_INFO_TYPE_ANY:
164                 OutputFormat("| %21s", "*");
165                 break;
166 
167             case OT_JOINER_INFO_TYPE_EUI64:
168                 OutputFormat("|      ");
169                 OutputExtAddress(joinerInfo.mSharedId.mEui64);
170                 break;
171 
172             case OT_JOINER_INFO_TYPE_DISCERNER:
173                 OutputFormat("| 0x%08lx%08lx/%2u",
174                              static_cast<unsigned long>(joinerInfo.mSharedId.mDiscerner.mValue >> 32),
175                              static_cast<unsigned long>(joinerInfo.mSharedId.mDiscerner.mValue & 0xffffffff),
176                              joinerInfo.mSharedId.mDiscerner.mLength);
177                 break;
178             }
179 
180             OutputFormat(" | %32s | %10lu |", joinerInfo.mPskd.m8, ToUlong(joinerInfo.mExpirationTime));
181             OutputNewLine();
182         }
183 
184         ExitNow(error = OT_ERROR_NONE);
185     }
186 
187     VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
188 
189     ClearAllBytes(discerner);
190 
191     if (aArgs[1] == "*")
192     {
193         // Intentionally empty
194     }
195     else
196     {
197         error = ParseJoinerDiscerner(aArgs[1], discerner);
198 
199         if (error == OT_ERROR_NOT_FOUND)
200         {
201             error   = aArgs[1].ParseAsHexString(addr.m8);
202             addrPtr = &addr;
203         }
204 
205         SuccessOrExit(error);
206     }
207 
208     /**
209      * @cli commissioner joiner add
210      * @code
211      * commissioner joiner add d45e64fa83f81cf7 J01NME
212      * Done
213      * @endcode
214      * @code
215      * commissioner joiner add 0xabc/12 J01NME
216      * Done
217      * @endcode
218      * @cparam commissioner joiner add @ca{eui64}|@ca{discerner pksd} [@ca{timeout}]
219      *   * `eui64`: IEEE EUI-64 of the %Joiner. To match any joiner, use `*`.
220      *   * `discerner`: The %Joiner discerner in the format `number/length`.
221      *   * `pksd`: Pre-Shared Key for the joiner.
222      *   * `timeout`: The %Joiner timeout in seconds.
223      * @par
224      * Adds a joiner entry.
225      * @note Use this command only after successfully starting the %Commissioner role
226      * with the `commissioner start` command.
227      * @csa{commissioner start}
228      * @sa otCommissionerAddJoiner
229      * @sa otCommissionerAddJoinerWithDiscerner
230      */
231     if (aArgs[0] == "add")
232     {
233         uint32_t timeout = kDefaultJoinerTimeout;
234 
235         VerifyOrExit(!aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
236 
237         if (!aArgs[3].IsEmpty())
238         {
239             SuccessOrExit(error = aArgs[3].ParseAsUint32(timeout));
240         }
241 
242         if (discerner.mLength)
243         {
244             error = otCommissionerAddJoinerWithDiscerner(GetInstancePtr(), &discerner, aArgs[2].GetCString(), timeout);
245         }
246         else
247         {
248             error = otCommissionerAddJoiner(GetInstancePtr(), addrPtr, aArgs[2].GetCString(), timeout);
249         }
250         /**
251          * @cli commissioner joiner remove
252          * @code
253          * commissioner joiner remove d45e64fa83f81cf7
254          * Done
255          * @endcode
256          * @code
257          * commissioner joiner remove 0xabc/12
258          * Done
259          * @endcode
260          * @cparam commissioner joiner remove @ca{eui64}|@ca{discerner}
261          *   * `eui64`: IEEE EUI-64 of the joiner. To match any joiner, use `*`.
262          *   * `discerner`: The joiner discerner in the format `number/length`.
263          * @par
264          * Removes a %Joiner entry.
265          * @note Use this command only after successfully starting the %Commissioner role
266          * with the `commissioner start` command.
267          * @csa{commissioner start}
268          * @sa otCommissionerRemoveJoiner
269          * @sa otCommissionerRemoveJoinerWithDiscerner
270          */
271     }
272     else if (aArgs[0] == "remove")
273     {
274         if (discerner.mLength)
275         {
276             error = otCommissionerRemoveJoinerWithDiscerner(GetInstancePtr(), &discerner);
277         }
278         else
279         {
280             error = otCommissionerRemoveJoiner(GetInstancePtr(), addrPtr);
281         }
282     }
283     else
284     {
285         error = OT_ERROR_INVALID_ARGS;
286     }
287 
288 exit:
289     return error;
290 }
291 
292 /**
293  * @cli commissioner mgmtget
294  * @code
295  * commissioner mgmtget locator sessionid
296  * Done
297  * @endcode
298  * @cparam commissioner mgmtget [locator] [sessionid] <!--
299  * -->                          [steeringdata] [joinerudpport] <!--
300  * -->                          [-x @ca{TLVs}]
301  *   * `locator`: Border Router RLOC16.
302  *   * `sessionid`: Session ID of the %Commissioner.
303  *   * `steeringdata`: Steering data.
304  *   * `joinerudpport`: %Joiner UDP port.
305  *   * `TLVs`: The set of TLVs to be retrieved.
306  * @par
307  * Sends a `MGMT_GET` (Management Get) message to the Leader.
308  * Variable values that have been set using the `commissioner mgmtset` command are returned.
309  * @sa otCommissionerSendMgmtGet
310  */
Process(Arg aArgs[])311 template <> otError Commissioner::Process<Cmd("mgmtget")>(Arg aArgs[])
312 {
313     otError error = OT_ERROR_NONE;
314     uint8_t tlvs[32];
315     uint8_t length = 0;
316 
317     for (; !aArgs->IsEmpty(); aArgs++)
318     {
319         VerifyOrExit(static_cast<size_t>(length) < sizeof(tlvs), error = OT_ERROR_NO_BUFS);
320 
321         if (*aArgs == "locator")
322         {
323             tlvs[length++] = OT_MESHCOP_TLV_BORDER_AGENT_RLOC;
324         }
325         else if (*aArgs == "sessionid")
326         {
327             tlvs[length++] = OT_MESHCOP_TLV_COMM_SESSION_ID;
328         }
329         else if (*aArgs == "steeringdata")
330         {
331             tlvs[length++] = OT_MESHCOP_TLV_STEERING_DATA;
332         }
333         else if (*aArgs == "joinerudpport")
334         {
335             tlvs[length++] = OT_MESHCOP_TLV_JOINER_UDP_PORT;
336         }
337         else if (*aArgs == "-x")
338         {
339             uint16_t readLength;
340 
341             aArgs++;
342             readLength = static_cast<uint16_t>(sizeof(tlvs) - length);
343             SuccessOrExit(error = aArgs->ParseAsHexString(readLength, tlvs + length));
344             length += static_cast<uint8_t>(readLength);
345         }
346         else
347         {
348             ExitNow(error = OT_ERROR_INVALID_ARGS);
349         }
350     }
351 
352     error = otCommissionerSendMgmtGet(GetInstancePtr(), tlvs, static_cast<uint8_t>(length));
353 
354 exit:
355     return error;
356 }
357 
358 /**
359  * @cli commissioner mgmtset
360  * @code
361  * commissioner mgmtset joinerudpport 9988
362  * Done
363  * @endcode
364  * @cparam commissioner mgmtset [locator @ca{locator}] [sessionid @ca{sessionid}] <!--
365  * -->                          [steeringdata @ca{steeringdata}] [joinerudpport @ca{joinerudpport}] <!--
366  * -->                          [-x @ca{TLVs}]
367  *   * `locator`: Border Router RLOC16.
368  *   * `sessionid`: Session ID of the %Commissioner.
369  *   * `steeringdata`: Steering data.
370  *   * `joinerudpport`: %Joiner UDP port.
371  *   * `TLVs`: The set of TLVs to be retrieved.
372  * @par
373  * Sends a `MGMT_SET` (Management Set) message to the Leader, and sets the
374  * variables to the values specified.
375  * @sa otCommissionerSendMgmtSet
376  */
Process(Arg aArgs[])377 template <> otError Commissioner::Process<Cmd("mgmtset")>(Arg aArgs[])
378 {
379     otError                error;
380     otCommissioningDataset dataset;
381     uint8_t                tlvs[32];
382     uint8_t                tlvsLength = 0;
383 
384     VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
385 
386     ClearAllBytes(dataset);
387 
388     for (; !aArgs->IsEmpty(); aArgs++)
389     {
390         if (*aArgs == "locator")
391         {
392             aArgs++;
393             dataset.mIsLocatorSet = true;
394             SuccessOrExit(error = aArgs->ParseAsUint16(dataset.mLocator));
395         }
396         else if (*aArgs == "sessionid")
397         {
398             aArgs++;
399             dataset.mIsSessionIdSet = true;
400             SuccessOrExit(error = aArgs->ParseAsUint16(dataset.mSessionId));
401         }
402         else if (*aArgs == "steeringdata")
403         {
404             uint16_t length;
405 
406             aArgs++;
407             dataset.mIsSteeringDataSet = true;
408             length                     = sizeof(dataset.mSteeringData.m8);
409             SuccessOrExit(error = aArgs->ParseAsHexString(length, dataset.mSteeringData.m8));
410             dataset.mSteeringData.mLength = static_cast<uint8_t>(length);
411         }
412         else if (*aArgs == "joinerudpport")
413         {
414             aArgs++;
415             dataset.mIsJoinerUdpPortSet = true;
416             SuccessOrExit(error = aArgs->ParseAsUint16(dataset.mJoinerUdpPort));
417         }
418         else if (*aArgs == "-x")
419         {
420             uint16_t length;
421 
422             aArgs++;
423             length = sizeof(tlvs);
424             SuccessOrExit(error = aArgs->ParseAsHexString(length, tlvs));
425             tlvsLength = static_cast<uint8_t>(length);
426         }
427         else
428         {
429             ExitNow(error = OT_ERROR_INVALID_ARGS);
430         }
431     }
432 
433     error = otCommissionerSendMgmtSet(GetInstancePtr(), &dataset, tlvs, tlvsLength);
434 
435 exit:
436     return error;
437 }
438 
439 /**
440  * @cli commissioner panid
441  * @code
442  * commissioner panid 0xdead 0x7fff800 fdde:ad00:beef:0:0:ff:fe00:c00
443  * Done
444  * Conflict: dead, 00000800
445  * @endcode
446  * @cparam commissioner panid @ca{panid} @ca{mask} @ca{destination}
447  *   * `paind`: PAN ID to use to check for conflicts.
448  *   * `mask`; Bitmask that identifies channels to perform IEEE 802.15.4
449  *     Active Scans.
450  *   * `destination`: IPv6 destination address for the message. The message may be multicast.
451  * @par
452  * Sends a PAN ID query. Command output is returned as it is received.
453  * @note Use this command only after successfully starting the %Commissioner role
454  * with the `commissioner start` command.
455  * @csa{commissioner start}
456  * @sa otCommissionerPanIdQuery
457  */
Process(Arg aArgs[])458 template <> otError Commissioner::Process<Cmd("panid")>(Arg aArgs[])
459 {
460     otError      error;
461     uint16_t     panId;
462     uint32_t     mask;
463     otIp6Address address;
464 
465     SuccessOrExit(error = aArgs[0].ParseAsUint16(panId));
466     SuccessOrExit(error = aArgs[1].ParseAsUint32(mask));
467     SuccessOrExit(error = aArgs[2].ParseAsIp6Address(address));
468 
469     error = otCommissionerPanIdQuery(GetInstancePtr(), panId, mask, &address, &Commissioner::HandlePanIdConflict, this);
470 
471 exit:
472     return error;
473 }
474 
475 /**
476  * @cli commissioner provisioningurl
477  * @code
478  * commissioner provisioningurl http://github.com/openthread/openthread
479  * Done
480  * @endcode
481  * @cparam commissioner provisioningurl @ca{provisioningurl}
482  * @par
483  * Sets the %Commissioner provisioning URL.
484  * @sa otCommissionerSetProvisioningUrl
485  */
Process(Arg aArgs[])486 template <> otError Commissioner::Process<Cmd("provisioningurl")>(Arg aArgs[])
487 {
488     // If aArgs[0] is empty, `GetCString() will return `nullptr`
489     /// which will correctly clear the provisioning URL.
490     return otCommissionerSetProvisioningUrl(GetInstancePtr(), aArgs[0].GetCString());
491 }
492 
493 /**
494  * @cli commissioner sessionid
495  * @code
496  * commissioner sessionid
497  * 0
498  * Done
499  * @endcode
500  * @par
501  * Gets the current %Commissioner session ID.
502  * @sa otCommissionerGetSessionId
503  */
Process(Arg aArgs[])504 template <> otError Commissioner::Process<Cmd("sessionid")>(Arg aArgs[])
505 {
506     OT_UNUSED_VARIABLE(aArgs);
507 
508     OutputLine("%d", otCommissionerGetSessionId(GetInstancePtr()));
509 
510     return OT_ERROR_NONE;
511 }
512 
513 /**
514  * @cli commissioner id (get,set)
515  * @code
516  * commissioner id OpenThread Commissioner
517  * Done
518  * @endcode
519  * @code
520  * commissioner id
521  * OpenThread Commissioner
522  * Done
523  * @endcode
524  * @cparam commissioner id @ca{name}
525  * @par
526  * Gets or sets the OpenThread %Commissioner ID name.
527  * @sa otCommissionerSetId
528  */
Process(Arg aArgs[])529 template <> otError Commissioner::Process<Cmd("id")>(Arg aArgs[])
530 {
531     otError error;
532 
533     if (aArgs[0].IsEmpty())
534     {
535         OutputLine("%s", otCommissionerGetId(GetInstancePtr()));
536         error = OT_ERROR_NONE;
537     }
538     else
539     {
540         error = otCommissionerSetId(GetInstancePtr(), aArgs[0].GetCString());
541     }
542 
543     return error;
544 }
545 
546 /**
547  * @cli commissioner start
548  * @code
549  * commissioner start
550  * Commissioner: petitioning
551  * Done
552  * Commissioner: active
553  * @endcode
554  * @par
555  * Starts the Thread %Commissioner role.
556  * @note The `commissioner` commands are available only when
557  * `OPENTHREAD_CONFIG_COMMISSIONER_ENABLE` and `OPENTHREAD_FTD` are set.
558  * @sa otCommissionerStart
559  */
Process(Arg aArgs[])560 template <> otError Commissioner::Process<Cmd("start")>(Arg aArgs[])
561 {
562     OT_UNUSED_VARIABLE(aArgs);
563 
564     return otCommissionerStart(GetInstancePtr(), &Commissioner::HandleStateChanged, &Commissioner::HandleJoinerEvent,
565                                this);
566 }
567 
HandleStateChanged(otCommissionerState aState,void * aContext)568 void Commissioner::HandleStateChanged(otCommissionerState aState, void *aContext)
569 {
570     static_cast<Commissioner *>(aContext)->HandleStateChanged(aState);
571 }
572 
HandleStateChanged(otCommissionerState aState)573 void Commissioner::HandleStateChanged(otCommissionerState aState)
574 {
575     OutputLine("Commissioner: %s", StateToString(aState));
576 }
577 
StateToString(otCommissionerState aState)578 const char *Commissioner::StateToString(otCommissionerState aState)
579 {
580     static const char *const kStateString[] = {
581         "disabled",    // (0) OT_COMMISSIONER_STATE_DISABLED
582         "petitioning", // (1) OT_COMMISSIONER_STATE_PETITION
583         "active",      // (2) OT_COMMISSIONER_STATE_ACTIVE
584     };
585 
586     static_assert(0 == OT_COMMISSIONER_STATE_DISABLED, "OT_COMMISSIONER_STATE_DISABLED value is incorrect");
587     static_assert(1 == OT_COMMISSIONER_STATE_PETITION, "OT_COMMISSIONER_STATE_PETITION value is incorrect");
588     static_assert(2 == OT_COMMISSIONER_STATE_ACTIVE, "OT_COMMISSIONER_STATE_ACTIVE value is incorrect");
589 
590     return Stringify(aState, kStateString);
591 }
592 
HandleJoinerEvent(otCommissionerJoinerEvent aEvent,const otJoinerInfo * aJoinerInfo,const otExtAddress * aJoinerId,void * aContext)593 void Commissioner::HandleJoinerEvent(otCommissionerJoinerEvent aEvent,
594                                      const otJoinerInfo       *aJoinerInfo,
595                                      const otExtAddress       *aJoinerId,
596                                      void                     *aContext)
597 {
598     static_cast<Commissioner *>(aContext)->HandleJoinerEvent(aEvent, aJoinerInfo, aJoinerId);
599 }
600 
HandleJoinerEvent(otCommissionerJoinerEvent aEvent,const otJoinerInfo * aJoinerInfo,const otExtAddress * aJoinerId)601 void Commissioner::HandleJoinerEvent(otCommissionerJoinerEvent aEvent,
602                                      const otJoinerInfo       *aJoinerInfo,
603                                      const otExtAddress       *aJoinerId)
604 {
605     static const char *const kEventStrings[] = {
606         "start",    // (0) OT_COMMISSIONER_JOINER_START
607         "connect",  // (1) OT_COMMISSIONER_JOINER_CONNECTED
608         "finalize", // (2) OT_COMMISSIONER_JOINER_FINALIZE
609         "end",      // (3) OT_COMMISSIONER_JOINER_END
610         "remove",   // (4) OT_COMMISSIONER_JOINER_REMOVED
611     };
612 
613     static_assert(0 == OT_COMMISSIONER_JOINER_START, "OT_COMMISSIONER_JOINER_START value is incorrect");
614     static_assert(1 == OT_COMMISSIONER_JOINER_CONNECTED, "OT_COMMISSIONER_JOINER_CONNECTED value is incorrect");
615     static_assert(2 == OT_COMMISSIONER_JOINER_FINALIZE, "OT_COMMISSIONER_JOINER_FINALIZE value is incorrect");
616     static_assert(3 == OT_COMMISSIONER_JOINER_END, "OT_COMMISSIONER_JOINER_END value is incorrect");
617     static_assert(4 == OT_COMMISSIONER_JOINER_REMOVED, "OT_COMMISSIONER_JOINER_REMOVED value is incorrect");
618 
619     OT_UNUSED_VARIABLE(aJoinerInfo);
620 
621     OutputFormat("Commissioner: Joiner %s ", Stringify(aEvent, kEventStrings));
622 
623     if (aJoinerId != nullptr)
624     {
625         OutputExtAddress(*aJoinerId);
626     }
627 
628     OutputNewLine();
629 }
630 
631 /**
632  * @cli commissioner stop
633  * @code
634  * commissioner stop
635  * Done
636  * @endcode
637  * @par
638  * Stops the Thread %Commissioner role.
639  * @sa otCommissionerStop
640  */
Process(Arg aArgs[])641 template <> otError Commissioner::Process<Cmd("stop")>(Arg aArgs[])
642 {
643     OT_UNUSED_VARIABLE(aArgs);
644 
645     return otCommissionerStop(GetInstancePtr());
646 }
647 
648 /**
649  * @cli commissioner state
650  * @code
651  * commissioner state
652  * active
653  * Done
654  * @endcode
655  * @par
656  * Returns the current state of the %Commissioner. Possible values are
657  * `active`, `disabled`, or `petition` (petitioning to become %Commissioner).
658  * @sa otCommissionerState
659  */
Process(Arg aArgs[])660 template <> otError Commissioner::Process<Cmd("state")>(Arg aArgs[])
661 {
662     OT_UNUSED_VARIABLE(aArgs);
663 
664     OutputLine("%s", StateToString(otCommissionerGetState(GetInstancePtr())));
665 
666     return OT_ERROR_NONE;
667 }
668 
Process(Arg aArgs[])669 otError Commissioner::Process(Arg aArgs[])
670 {
671 #define CmdEntry(aCommandString)                                    \
672     {                                                               \
673         aCommandString, &Commissioner::Process<Cmd(aCommandString)> \
674     }
675 
676     static constexpr Command kCommands[] = {
677         CmdEntry("announce"),  CmdEntry("energy"),  CmdEntry("id"),    CmdEntry("joiner"),
678         CmdEntry("mgmtget"),   CmdEntry("mgmtset"), CmdEntry("panid"), CmdEntry("provisioningurl"),
679         CmdEntry("sessionid"), CmdEntry("start"),   CmdEntry("state"), CmdEntry("stop"),
680     };
681 
682 #undef CmdEntry
683 
684     static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
685 
686     otError        error = OT_ERROR_INVALID_COMMAND;
687     const Command *command;
688 
689     if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
690     {
691         OutputCommandTable(kCommands);
692         ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
693     }
694 
695     command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
696     VerifyOrExit(command != nullptr);
697 
698     error = (this->*command->mHandler)(aArgs + 1);
699 
700 exit:
701     return error;
702 }
703 
HandleEnergyReport(uint32_t aChannelMask,const uint8_t * aEnergyList,uint8_t aEnergyListLength,void * aContext)704 void Commissioner::HandleEnergyReport(uint32_t       aChannelMask,
705                                       const uint8_t *aEnergyList,
706                                       uint8_t        aEnergyListLength,
707                                       void          *aContext)
708 {
709     static_cast<Commissioner *>(aContext)->HandleEnergyReport(aChannelMask, aEnergyList, aEnergyListLength);
710 }
711 
HandleEnergyReport(uint32_t aChannelMask,const uint8_t * aEnergyList,uint8_t aEnergyListLength)712 void Commissioner::HandleEnergyReport(uint32_t aChannelMask, const uint8_t *aEnergyList, uint8_t aEnergyListLength)
713 {
714     OutputFormat("Energy: %08lx ", ToUlong(aChannelMask));
715 
716     for (uint8_t i = 0; i < aEnergyListLength; i++)
717     {
718         OutputFormat("%d ", static_cast<int8_t>(aEnergyList[i]));
719     }
720 
721     OutputNewLine();
722 }
723 
HandlePanIdConflict(uint16_t aPanId,uint32_t aChannelMask,void * aContext)724 void Commissioner::HandlePanIdConflict(uint16_t aPanId, uint32_t aChannelMask, void *aContext)
725 {
726     static_cast<Commissioner *>(aContext)->HandlePanIdConflict(aPanId, aChannelMask);
727 }
728 
HandlePanIdConflict(uint16_t aPanId,uint32_t aChannelMask)729 void Commissioner::HandlePanIdConflict(uint16_t aPanId, uint32_t aChannelMask)
730 {
731     OutputLine("Conflict: %04x, %08lx", aPanId, ToUlong(aChannelMask));
732 }
733 
734 } // namespace Cli
735 } // namespace ot
736 
737 #endif // OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD
738