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 
Process(Arg aArgs[])43 template <> otError Commissioner::Process<Cmd("announce")>(Arg aArgs[])
44 {
45     otError      error;
46     uint32_t     mask;
47     uint8_t      count;
48     uint16_t     period;
49     otIp6Address address;
50 
51     SuccessOrExit(error = aArgs[0].ParseAsUint32(mask));
52     SuccessOrExit(error = aArgs[1].ParseAsUint8(count));
53     SuccessOrExit(error = aArgs[2].ParseAsUint16(period));
54     SuccessOrExit(error = aArgs[3].ParseAsIp6Address(address));
55 
56     error = otCommissionerAnnounceBegin(GetInstancePtr(), mask, count, period, &address);
57 
58 exit:
59     return error;
60 }
61 
Process(Arg aArgs[])62 template <> otError Commissioner::Process<Cmd("energy")>(Arg aArgs[])
63 {
64     otError      error;
65     uint32_t     mask;
66     uint8_t      count;
67     uint16_t     period;
68     uint16_t     scanDuration;
69     otIp6Address address;
70 
71     SuccessOrExit(error = aArgs[0].ParseAsUint32(mask));
72     SuccessOrExit(error = aArgs[1].ParseAsUint8(count));
73     SuccessOrExit(error = aArgs[2].ParseAsUint16(period));
74     SuccessOrExit(error = aArgs[3].ParseAsUint16(scanDuration));
75     SuccessOrExit(error = aArgs[4].ParseAsIp6Address(address));
76 
77     error = otCommissionerEnergyScan(GetInstancePtr(), mask, count, period, scanDuration, &address,
78                                      &Commissioner::HandleEnergyReport, this);
79 
80 exit:
81     return error;
82 }
83 
Process(Arg aArgs[])84 template <> otError Commissioner::Process<Cmd("joiner")>(Arg aArgs[])
85 {
86     otError             error = OT_ERROR_NONE;
87     otExtAddress        addr;
88     const otExtAddress *addrPtr = nullptr;
89     otJoinerDiscerner   discerner;
90 
91     if (aArgs[0] == "table")
92     {
93         uint16_t     iter = 0;
94         otJoinerInfo joinerInfo;
95 
96         static const char *const kJoinerTableTitles[] = {"ID", "PSKd", "Expiration"};
97 
98         static const uint8_t kJoinerTableColumnWidths[] = {
99             23,
100             34,
101             12,
102         };
103 
104         OutputTableHeader(kJoinerTableTitles, kJoinerTableColumnWidths);
105 
106         while (otCommissionerGetNextJoinerInfo(GetInstancePtr(), &iter, &joinerInfo) == OT_ERROR_NONE)
107         {
108             switch (joinerInfo.mType)
109             {
110             case OT_JOINER_INFO_TYPE_ANY:
111                 OutputFormat("| %21s", "*");
112                 break;
113 
114             case OT_JOINER_INFO_TYPE_EUI64:
115                 OutputFormat("|      ");
116                 OutputExtAddress(joinerInfo.mSharedId.mEui64);
117                 break;
118 
119             case OT_JOINER_INFO_TYPE_DISCERNER:
120                 OutputFormat("| 0x%08lx%08lx/%2u",
121                              static_cast<unsigned long>(joinerInfo.mSharedId.mDiscerner.mValue >> 32),
122                              static_cast<unsigned long>(joinerInfo.mSharedId.mDiscerner.mValue & 0xffffffff),
123                              joinerInfo.mSharedId.mDiscerner.mLength);
124                 break;
125             }
126 
127             OutputFormat(" | %32s | %10lu |", joinerInfo.mPskd.m8, ToUlong(joinerInfo.mExpirationTime));
128             OutputNewLine();
129         }
130 
131         ExitNow(error = OT_ERROR_NONE);
132     }
133 
134     VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
135 
136     memset(&discerner, 0, sizeof(discerner));
137 
138     if (aArgs[1] == "*")
139     {
140         // Intentionally empty
141     }
142     else
143     {
144         error = Interpreter::ParseJoinerDiscerner(aArgs[1], discerner);
145 
146         if (error == OT_ERROR_NOT_FOUND)
147         {
148             error   = aArgs[1].ParseAsHexString(addr.m8);
149             addrPtr = &addr;
150         }
151 
152         SuccessOrExit(error);
153     }
154 
155     if (aArgs[0] == "add")
156     {
157         uint32_t timeout = kDefaultJoinerTimeout;
158 
159         VerifyOrExit(!aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
160 
161         if (!aArgs[3].IsEmpty())
162         {
163             SuccessOrExit(error = aArgs[3].ParseAsUint32(timeout));
164         }
165 
166         if (discerner.mLength)
167         {
168             error = otCommissionerAddJoinerWithDiscerner(GetInstancePtr(), &discerner, aArgs[2].GetCString(), timeout);
169         }
170         else
171         {
172             error = otCommissionerAddJoiner(GetInstancePtr(), addrPtr, aArgs[2].GetCString(), timeout);
173         }
174     }
175     else if (aArgs[0] == "remove")
176     {
177         if (discerner.mLength)
178         {
179             error = otCommissionerRemoveJoinerWithDiscerner(GetInstancePtr(), &discerner);
180         }
181         else
182         {
183             error = otCommissionerRemoveJoiner(GetInstancePtr(), addrPtr);
184         }
185     }
186     else
187     {
188         error = OT_ERROR_INVALID_ARGS;
189     }
190 
191 exit:
192     return error;
193 }
194 
Process(Arg aArgs[])195 template <> otError Commissioner::Process<Cmd("mgmtget")>(Arg aArgs[])
196 {
197     otError error = OT_ERROR_NONE;
198     uint8_t tlvs[32];
199     uint8_t length = 0;
200 
201     for (; !aArgs->IsEmpty(); aArgs++)
202     {
203         VerifyOrExit(static_cast<size_t>(length) < sizeof(tlvs), error = OT_ERROR_NO_BUFS);
204 
205         if (*aArgs == "locator")
206         {
207             tlvs[length++] = OT_MESHCOP_TLV_BORDER_AGENT_RLOC;
208         }
209         else if (*aArgs == "sessionid")
210         {
211             tlvs[length++] = OT_MESHCOP_TLV_COMM_SESSION_ID;
212         }
213         else if (*aArgs == "steeringdata")
214         {
215             tlvs[length++] = OT_MESHCOP_TLV_STEERING_DATA;
216         }
217         else if (*aArgs == "joinerudpport")
218         {
219             tlvs[length++] = OT_MESHCOP_TLV_JOINER_UDP_PORT;
220         }
221         else if (*aArgs == "-x")
222         {
223             uint16_t readLength;
224 
225             aArgs++;
226             readLength = static_cast<uint16_t>(sizeof(tlvs) - length);
227             SuccessOrExit(error = aArgs->ParseAsHexString(readLength, tlvs + length));
228             length += static_cast<uint8_t>(readLength);
229         }
230         else
231         {
232             ExitNow(error = OT_ERROR_INVALID_ARGS);
233         }
234     }
235 
236     error = otCommissionerSendMgmtGet(GetInstancePtr(), tlvs, static_cast<uint8_t>(length));
237 
238 exit:
239     return error;
240 }
241 
Process(Arg aArgs[])242 template <> otError Commissioner::Process<Cmd("mgmtset")>(Arg aArgs[])
243 {
244     otError                error;
245     otCommissioningDataset dataset;
246     uint8_t                tlvs[32];
247     uint8_t                tlvsLength = 0;
248 
249     VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
250 
251     memset(&dataset, 0, sizeof(dataset));
252 
253     for (; !aArgs->IsEmpty(); aArgs++)
254     {
255         if (*aArgs == "locator")
256         {
257             aArgs++;
258             dataset.mIsLocatorSet = true;
259             SuccessOrExit(error = aArgs->ParseAsUint16(dataset.mLocator));
260         }
261         else if (*aArgs == "sessionid")
262         {
263             aArgs++;
264             dataset.mIsSessionIdSet = true;
265             SuccessOrExit(error = aArgs->ParseAsUint16(dataset.mSessionId));
266         }
267         else if (*aArgs == "steeringdata")
268         {
269             uint16_t length;
270 
271             aArgs++;
272             dataset.mIsSteeringDataSet = true;
273             length                     = sizeof(dataset.mSteeringData.m8);
274             SuccessOrExit(error = aArgs->ParseAsHexString(length, dataset.mSteeringData.m8));
275             dataset.mSteeringData.mLength = static_cast<uint8_t>(length);
276         }
277         else if (*aArgs == "joinerudpport")
278         {
279             aArgs++;
280             dataset.mIsJoinerUdpPortSet = true;
281             SuccessOrExit(error = aArgs->ParseAsUint16(dataset.mJoinerUdpPort));
282         }
283         else if (*aArgs == "-x")
284         {
285             uint16_t length;
286 
287             aArgs++;
288             length = sizeof(tlvs);
289             SuccessOrExit(error = aArgs->ParseAsHexString(length, tlvs));
290             tlvsLength = static_cast<uint8_t>(length);
291         }
292         else
293         {
294             ExitNow(error = OT_ERROR_INVALID_ARGS);
295         }
296     }
297 
298     error = otCommissionerSendMgmtSet(GetInstancePtr(), &dataset, tlvs, tlvsLength);
299 
300 exit:
301     return error;
302 }
303 
Process(Arg aArgs[])304 template <> otError Commissioner::Process<Cmd("panid")>(Arg aArgs[])
305 {
306     otError      error;
307     uint16_t     panId;
308     uint32_t     mask;
309     otIp6Address address;
310 
311     SuccessOrExit(error = aArgs[0].ParseAsUint16(panId));
312     SuccessOrExit(error = aArgs[1].ParseAsUint32(mask));
313     SuccessOrExit(error = aArgs[2].ParseAsIp6Address(address));
314 
315     error = otCommissionerPanIdQuery(GetInstancePtr(), panId, mask, &address, &Commissioner::HandlePanIdConflict, this);
316 
317 exit:
318     return error;
319 }
320 
Process(Arg aArgs[])321 template <> otError Commissioner::Process<Cmd("provisioningurl")>(Arg aArgs[])
322 {
323     // If aArgs[0] is empty, `GetCString() will return `nullptr`
324     /// which will correctly clear the provisioning URL.
325     return otCommissionerSetProvisioningUrl(GetInstancePtr(), aArgs[0].GetCString());
326 }
327 
Process(Arg aArgs[])328 template <> otError Commissioner::Process<Cmd("sessionid")>(Arg aArgs[])
329 {
330     OT_UNUSED_VARIABLE(aArgs);
331 
332     OutputLine("%d", otCommissionerGetSessionId(GetInstancePtr()));
333 
334     return OT_ERROR_NONE;
335 }
336 
Process(Arg aArgs[])337 template <> otError Commissioner::Process<Cmd("id")>(Arg aArgs[])
338 {
339     otError error;
340 
341     if (aArgs[0].IsEmpty())
342     {
343         OutputLine("%s", otCommissionerGetId(GetInstancePtr()));
344         error = OT_ERROR_NONE;
345     }
346     else
347     {
348         error = otCommissionerSetId(GetInstancePtr(), aArgs[0].GetCString());
349     }
350 
351     return error;
352 }
353 
Process(Arg aArgs[])354 template <> otError Commissioner::Process<Cmd("start")>(Arg aArgs[])
355 {
356     OT_UNUSED_VARIABLE(aArgs);
357 
358     return otCommissionerStart(GetInstancePtr(), &Commissioner::HandleStateChanged, &Commissioner::HandleJoinerEvent,
359                                this);
360 }
361 
HandleStateChanged(otCommissionerState aState,void * aContext)362 void Commissioner::HandleStateChanged(otCommissionerState aState, void *aContext)
363 {
364     static_cast<Commissioner *>(aContext)->HandleStateChanged(aState);
365 }
366 
HandleStateChanged(otCommissionerState aState)367 void Commissioner::HandleStateChanged(otCommissionerState aState)
368 {
369     OutputLine("Commissioner: %s", StateToString(aState));
370 }
371 
StateToString(otCommissionerState aState)372 const char *Commissioner::StateToString(otCommissionerState aState)
373 {
374     static const char *const kStateString[] = {
375         "disabled",    // (0) OT_COMMISSIONER_STATE_DISABLED
376         "petitioning", // (1) OT_COMMISSIONER_STATE_PETITION
377         "active",      // (2) OT_COMMISSIONER_STATE_ACTIVE
378     };
379 
380     static_assert(0 == OT_COMMISSIONER_STATE_DISABLED, "OT_COMMISSIONER_STATE_DISABLED value is incorrect");
381     static_assert(1 == OT_COMMISSIONER_STATE_PETITION, "OT_COMMISSIONER_STATE_PETITION value is incorrect");
382     static_assert(2 == OT_COMMISSIONER_STATE_ACTIVE, "OT_COMMISSIONER_STATE_ACTIVE value is incorrect");
383 
384     return Stringify(aState, kStateString);
385 }
386 
HandleJoinerEvent(otCommissionerJoinerEvent aEvent,const otJoinerInfo * aJoinerInfo,const otExtAddress * aJoinerId,void * aContext)387 void Commissioner::HandleJoinerEvent(otCommissionerJoinerEvent aEvent,
388                                      const otJoinerInfo       *aJoinerInfo,
389                                      const otExtAddress       *aJoinerId,
390                                      void                     *aContext)
391 {
392     static_cast<Commissioner *>(aContext)->HandleJoinerEvent(aEvent, aJoinerInfo, aJoinerId);
393 }
394 
HandleJoinerEvent(otCommissionerJoinerEvent aEvent,const otJoinerInfo * aJoinerInfo,const otExtAddress * aJoinerId)395 void Commissioner::HandleJoinerEvent(otCommissionerJoinerEvent aEvent,
396                                      const otJoinerInfo       *aJoinerInfo,
397                                      const otExtAddress       *aJoinerId)
398 {
399     static const char *const kEventStrings[] = {
400         "start",    // (0) OT_COMMISSIONER_JOINER_START
401         "connect",  // (1) OT_COMMISSIONER_JOINER_CONNECTED
402         "finalize", // (2) OT_COMMISSIONER_JOINER_FINALIZE
403         "end",      // (3) OT_COMMISSIONER_JOINER_END
404         "remove",   // (4) OT_COMMISSIONER_JOINER_REMOVED
405     };
406 
407     static_assert(0 == OT_COMMISSIONER_JOINER_START, "OT_COMMISSIONER_JOINER_START value is incorrect");
408     static_assert(1 == OT_COMMISSIONER_JOINER_CONNECTED, "OT_COMMISSIONER_JOINER_CONNECTED value is incorrect");
409     static_assert(2 == OT_COMMISSIONER_JOINER_FINALIZE, "OT_COMMISSIONER_JOINER_FINALIZE value is incorrect");
410     static_assert(3 == OT_COMMISSIONER_JOINER_END, "OT_COMMISSIONER_JOINER_END value is incorrect");
411     static_assert(4 == OT_COMMISSIONER_JOINER_REMOVED, "OT_COMMISSIONER_JOINER_REMOVED value is incorrect");
412 
413     OT_UNUSED_VARIABLE(aJoinerInfo);
414 
415     OutputFormat("Commissioner: Joiner %s ", Stringify(aEvent, kEventStrings));
416 
417     if (aJoinerId != nullptr)
418     {
419         OutputExtAddress(*aJoinerId);
420     }
421 
422     OutputNewLine();
423 }
424 
Process(Arg aArgs[])425 template <> otError Commissioner::Process<Cmd("stop")>(Arg aArgs[])
426 {
427     OT_UNUSED_VARIABLE(aArgs);
428 
429     return otCommissionerStop(GetInstancePtr());
430 }
431 
Process(Arg aArgs[])432 template <> otError Commissioner::Process<Cmd("state")>(Arg aArgs[])
433 {
434     OT_UNUSED_VARIABLE(aArgs);
435 
436     OutputLine("%s", StateToString(otCommissionerGetState(GetInstancePtr())));
437 
438     return OT_ERROR_NONE;
439 }
440 
Process(Arg aArgs[])441 otError Commissioner::Process(Arg aArgs[])
442 {
443 #define CmdEntry(aCommandString)                                    \
444     {                                                               \
445         aCommandString, &Commissioner::Process<Cmd(aCommandString)> \
446     }
447 
448     static constexpr Command kCommands[] = {
449         CmdEntry("announce"),  CmdEntry("energy"),  CmdEntry("id"),    CmdEntry("joiner"),
450         CmdEntry("mgmtget"),   CmdEntry("mgmtset"), CmdEntry("panid"), CmdEntry("provisioningurl"),
451         CmdEntry("sessionid"), CmdEntry("start"),   CmdEntry("state"), CmdEntry("stop"),
452     };
453 
454 #undef CmdEntry
455 
456     static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
457 
458     otError        error = OT_ERROR_INVALID_COMMAND;
459     const Command *command;
460 
461     if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
462     {
463         OutputCommandTable(kCommands);
464         ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
465     }
466 
467     command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
468     VerifyOrExit(command != nullptr);
469 
470     error = (this->*command->mHandler)(aArgs + 1);
471 
472 exit:
473     return error;
474 }
475 
HandleEnergyReport(uint32_t aChannelMask,const uint8_t * aEnergyList,uint8_t aEnergyListLength,void * aContext)476 void Commissioner::HandleEnergyReport(uint32_t       aChannelMask,
477                                       const uint8_t *aEnergyList,
478                                       uint8_t        aEnergyListLength,
479                                       void          *aContext)
480 {
481     static_cast<Commissioner *>(aContext)->HandleEnergyReport(aChannelMask, aEnergyList, aEnergyListLength);
482 }
483 
HandleEnergyReport(uint32_t aChannelMask,const uint8_t * aEnergyList,uint8_t aEnergyListLength)484 void Commissioner::HandleEnergyReport(uint32_t aChannelMask, const uint8_t *aEnergyList, uint8_t aEnergyListLength)
485 {
486     OutputFormat("Energy: %08lx ", ToUlong(aChannelMask));
487 
488     for (uint8_t i = 0; i < aEnergyListLength; i++)
489     {
490         OutputFormat("%d ", static_cast<int8_t>(aEnergyList[i]));
491     }
492 
493     OutputNewLine();
494 }
495 
HandlePanIdConflict(uint16_t aPanId,uint32_t aChannelMask,void * aContext)496 void Commissioner::HandlePanIdConflict(uint16_t aPanId, uint32_t aChannelMask, void *aContext)
497 {
498     static_cast<Commissioner *>(aContext)->HandlePanIdConflict(aPanId, aChannelMask);
499 }
500 
HandlePanIdConflict(uint16_t aPanId,uint32_t aChannelMask)501 void Commissioner::HandlePanIdConflict(uint16_t aPanId, uint32_t aChannelMask)
502 {
503     OutputLine("Conflict: %04x, %08lx", aPanId, ToUlong(aChannelMask));
504 }
505 
506 } // namespace Cli
507 } // namespace ot
508 
509 #endif // OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD
510