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