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