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