1 /*
2 * Copyright (c) 2020, 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 SRP Client.
32 */
33
34 #include "cli_srp_client.hpp"
35
36 #if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
37
38 #include <string.h>
39
40 #include "cli/cli.hpp"
41
42 namespace ot {
43 namespace Cli {
44
45 constexpr SrpClient::Command SrpClient::sCommands[];
46
CopyString(char * aDest,uint16_t aDestSize,const char * aSource)47 static otError CopyString(char *aDest, uint16_t aDestSize, const char *aSource)
48 {
49 // Copies a string from `aSource` to `aDestination` (char array),
50 // verifying that the string fits in the destination array.
51
52 otError error = OT_ERROR_NONE;
53 size_t len = strlen(aSource);
54
55 VerifyOrExit(len + 1 <= aDestSize, error = OT_ERROR_INVALID_ARGS);
56 memcpy(aDest, aSource, len + 1);
57
58 exit:
59 return error;
60 }
61
SrpClient(Interpreter & aInterpreter)62 SrpClient::SrpClient(Interpreter &aInterpreter)
63 : mInterpreter(aInterpreter)
64 , mCallbackEnabled(false)
65 {
66 otSrpClientSetCallback(mInterpreter.mInstance, SrpClient::HandleCallback, this);
67 }
68
Process(Arg aArgs[])69 otError SrpClient::Process(Arg aArgs[])
70 {
71 otError error = OT_ERROR_INVALID_COMMAND;
72 const Command *command;
73
74 if (aArgs[0].IsEmpty())
75 {
76 IgnoreError(ProcessHelp(aArgs));
77 ExitNow();
78 }
79
80 command = Utils::LookupTable::Find(aArgs[0].GetCString(), sCommands);
81 VerifyOrExit(command != nullptr);
82
83 error = (this->*command->mHandler)(aArgs + 1);
84
85 exit:
86 return error;
87 }
88
89 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
90
ProcessAutoStart(Arg aArgs[])91 otError SrpClient::ProcessAutoStart(Arg aArgs[])
92 {
93 otError error = OT_ERROR_NONE;
94 bool enable;
95
96 if (aArgs[0].IsEmpty())
97 {
98 mInterpreter.OutputEnabledDisabledStatus(otSrpClientIsAutoStartModeEnabled(mInterpreter.mInstance));
99 ExitNow();
100 }
101
102 SuccessOrExit(error = Interpreter::ParseEnableOrDisable(aArgs[0], enable));
103
104 if (enable)
105 {
106 otSrpClientEnableAutoStartMode(mInterpreter.mInstance, /* aCallback */ nullptr, /* aContext */ nullptr);
107 }
108 else
109 {
110 otSrpClientDisableAutoStartMode(mInterpreter.mInstance);
111 }
112
113 exit:
114 return error;
115 }
116
117 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
118
ProcessCallback(Arg aArgs[])119 otError SrpClient::ProcessCallback(Arg aArgs[])
120 {
121 otError error = OT_ERROR_NONE;
122
123 if (aArgs[0].IsEmpty())
124 {
125 mInterpreter.OutputEnabledDisabledStatus(mCallbackEnabled);
126 ExitNow();
127 }
128
129 error = Interpreter::ParseEnableOrDisable(aArgs[0], mCallbackEnabled);
130
131 exit:
132 return error;
133 }
134
ProcessHelp(Arg aArgs[])135 otError SrpClient::ProcessHelp(Arg aArgs[])
136 {
137 OT_UNUSED_VARIABLE(aArgs);
138
139 for (const Command &command : sCommands)
140 {
141 mInterpreter.OutputLine(command.mName);
142 }
143
144 return OT_ERROR_NONE;
145 }
146
ProcessHost(Arg aArgs[])147 otError SrpClient::ProcessHost(Arg aArgs[])
148 {
149 otError error = OT_ERROR_NONE;
150
151 if (aArgs[0].IsEmpty())
152 {
153 OutputHostInfo(0, *otSrpClientGetHostInfo(mInterpreter.mInstance));
154 ExitNow();
155 }
156
157 if (aArgs[0] == "name")
158 {
159 if (aArgs[1].IsEmpty())
160 {
161 const char *name = otSrpClientGetHostInfo(mInterpreter.mInstance)->mName;
162 mInterpreter.OutputLine("%s", (name != nullptr) ? name : "(null)");
163 }
164 else
165 {
166 uint16_t len;
167 uint16_t size;
168 char * hostName;
169
170 VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
171 hostName = otSrpClientBuffersGetHostNameString(mInterpreter.mInstance, &size);
172
173 len = aArgs[1].GetLength();
174 VerifyOrExit(len + 1 <= size, error = OT_ERROR_INVALID_ARGS);
175
176 // We first make sure we can set the name, and if so
177 // we copy it to the persisted string buffer and set
178 // the host name again now with the persisted buffer.
179 // This ensures that we do not overwrite a previous
180 // buffer with a host name that cannot be set.
181
182 SuccessOrExit(error = otSrpClientSetHostName(mInterpreter.mInstance, aArgs[1].GetCString()));
183 memcpy(hostName, aArgs[1].GetCString(), len + 1);
184
185 IgnoreError(otSrpClientSetHostName(mInterpreter.mInstance, hostName));
186 }
187 }
188 else if (aArgs[0] == "state")
189 {
190 VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
191 mInterpreter.OutputLine("%s",
192 otSrpClientItemStateToString(otSrpClientGetHostInfo(mInterpreter.mInstance)->mState));
193 }
194 else if (aArgs[0] == "address")
195 {
196 if (aArgs[1].IsEmpty())
197 {
198 const otSrpClientHostInfo *hostInfo = otSrpClientGetHostInfo(mInterpreter.mInstance);
199
200 for (uint8_t index = 0; index < hostInfo->mNumAddresses; index++)
201 {
202 mInterpreter.OutputIp6Address(hostInfo->mAddresses[index]);
203 mInterpreter.OutputLine("");
204 }
205 }
206 else
207 {
208 uint8_t numAddresses = 0;
209 otIp6Address addresses[kMaxHostAddresses];
210 uint8_t arrayLength;
211 otIp6Address *hostAddressArray;
212
213 hostAddressArray = otSrpClientBuffersGetHostAddressesArray(mInterpreter.mInstance, &arrayLength);
214
215 // We first make sure we can set the addresses, and if so
216 // we copy the address list into the persisted address array
217 // and set it again. This ensures that we do not overwrite
218 // a previous list before we know it is safe to set/change
219 // the address list.
220
221 if (arrayLength > kMaxHostAddresses)
222 {
223 arrayLength = kMaxHostAddresses;
224 }
225
226 for (Arg *arg = &aArgs[1]; !arg->IsEmpty(); arg++)
227 {
228 VerifyOrExit(numAddresses < arrayLength, error = OT_ERROR_NO_BUFS);
229 SuccessOrExit(error = arg->ParseAsIp6Address(addresses[numAddresses]));
230 numAddresses++;
231 }
232
233 SuccessOrExit(error = otSrpClientSetHostAddresses(mInterpreter.mInstance, addresses, numAddresses));
234
235 memcpy(hostAddressArray, addresses, numAddresses * sizeof(hostAddressArray[0]));
236 IgnoreError(otSrpClientSetHostAddresses(mInterpreter.mInstance, hostAddressArray, numAddresses));
237 }
238 }
239 else if (aArgs[0] == "remove")
240 {
241 bool removeKeyLease = false;
242 bool sendUnregToServer = false;
243
244 if (!aArgs[1].IsEmpty())
245 {
246 SuccessOrExit(error = aArgs[1].ParseAsBool(removeKeyLease));
247
248 if (!aArgs[2].IsEmpty())
249 {
250 SuccessOrExit(error = aArgs[2].ParseAsBool(sendUnregToServer));
251 VerifyOrExit(aArgs[3].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
252 }
253 }
254
255 error = otSrpClientRemoveHostAndServices(mInterpreter.mInstance, removeKeyLease, sendUnregToServer);
256 }
257 else if (aArgs[0] == "clear")
258 {
259 VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
260 otSrpClientClearHostAndServices(mInterpreter.mInstance);
261 otSrpClientBuffersFreeAllServices(mInterpreter.mInstance);
262 }
263 else
264 {
265 error = OT_ERROR_INVALID_COMMAND;
266 }
267
268 exit:
269 return error;
270 }
271
ProcessLeaseInterval(Arg aArgs[])272 otError SrpClient::ProcessLeaseInterval(Arg aArgs[])
273 {
274 return mInterpreter.ProcessGetSet(aArgs, otSrpClientGetLeaseInterval, otSrpClientSetLeaseInterval);
275 }
276
ProcessKeyLeaseInterval(Arg aArgs[])277 otError SrpClient::ProcessKeyLeaseInterval(Arg aArgs[])
278 {
279 return mInterpreter.ProcessGetSet(aArgs, otSrpClientGetKeyLeaseInterval, otSrpClientSetKeyLeaseInterval);
280 }
281
ProcessServer(Arg aArgs[])282 otError SrpClient::ProcessServer(Arg aArgs[])
283 {
284 otError error = OT_ERROR_NONE;
285 const otSockAddr *serverSockAddr = otSrpClientGetServerAddress(mInterpreter.mInstance);
286
287 if (aArgs[0].IsEmpty())
288 {
289 char string[OT_IP6_SOCK_ADDR_STRING_SIZE];
290
291 otIp6SockAddrToString(serverSockAddr, string, sizeof(string));
292 mInterpreter.OutputLine(string);
293 ExitNow();
294 }
295
296 VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
297
298 if (aArgs[0] == "address")
299 {
300 mInterpreter.OutputIp6Address(serverSockAddr->mAddress);
301 mInterpreter.OutputLine("");
302 }
303 else if (aArgs[0] == "port")
304 {
305 mInterpreter.OutputLine("%u", serverSockAddr->mPort);
306 }
307 else
308 {
309 error = OT_ERROR_INVALID_COMMAND;
310 }
311
312 exit:
313 return error;
314 }
315
ProcessService(Arg aArgs[])316 otError SrpClient::ProcessService(Arg aArgs[])
317 {
318 otError error = OT_ERROR_NONE;
319 bool isRemove;
320
321 if (aArgs[0].IsEmpty())
322 {
323 OutputServiceList(0, otSrpClientGetServices(mInterpreter.mInstance));
324 ExitNow();
325 }
326
327 if (aArgs[0] == "add")
328 {
329 error = ProcessServiceAdd(aArgs);
330 }
331 else if ((isRemove = (aArgs[0] == "remove")) || (aArgs[0] == "clear"))
332 {
333 // `remove`|`clear` <instance-name> <service-name>
334
335 const otSrpClientService *service;
336
337 VerifyOrExit(!aArgs[2].IsEmpty() && aArgs[3].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
338
339 for (service = otSrpClientGetServices(mInterpreter.mInstance); service != nullptr; service = service->mNext)
340 {
341 if ((aArgs[1] == service->mInstanceName) && (aArgs[2] == service->mName))
342 {
343 break;
344 }
345 }
346
347 VerifyOrExit(service != nullptr, error = OT_ERROR_NOT_FOUND);
348
349 if (isRemove)
350 {
351 error = otSrpClientRemoveService(mInterpreter.mInstance, const_cast<otSrpClientService *>(service));
352 }
353 else
354 {
355 SuccessOrExit(
356 error = otSrpClientClearService(mInterpreter.mInstance, const_cast<otSrpClientService *>(service)));
357
358 otSrpClientBuffersFreeService(mInterpreter.mInstance, reinterpret_cast<otSrpClientBuffersServiceEntry *>(
359 const_cast<otSrpClientService *>(service)));
360 }
361 }
362 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
363 else if (aArgs[0] == "key")
364 {
365 // `key [enable/disable]`
366
367 bool enable;
368
369 if (aArgs[1].IsEmpty())
370 {
371 mInterpreter.OutputEnabledDisabledStatus(otSrpClientIsServiceKeyRecordEnabled(mInterpreter.mInstance));
372 ExitNow();
373 }
374
375 SuccessOrExit(error = Interpreter::ParseEnableOrDisable(aArgs[1], enable));
376 VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
377 otSrpClientSetServiceKeyRecordEnabled(mInterpreter.mInstance, enable);
378 }
379 #endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
380 else
381 {
382 error = OT_ERROR_INVALID_COMMAND;
383 }
384
385 exit:
386 return error;
387 }
388
ProcessServiceAdd(Arg aArgs[])389 otError SrpClient::ProcessServiceAdd(Arg aArgs[])
390 {
391 // `add` <instance-name> <service-name> <port> [priority] [weight] [txt]
392
393 otSrpClientBuffersServiceEntry *entry = nullptr;
394 uint16_t size;
395 char * string;
396 otError error;
397 char * label;
398
399 entry = otSrpClientBuffersAllocateService(mInterpreter.mInstance);
400
401 VerifyOrExit(entry != nullptr, error = OT_ERROR_NO_BUFS);
402
403 SuccessOrExit(error = aArgs[3].ParseAsUint16(entry->mService.mPort));
404
405 // Successfully parsing aArgs[3] indicates that aArgs[1] and
406 // aArgs[2] are also non-empty.
407
408 string = otSrpClientBuffersGetServiceEntryInstanceNameString(entry, &size);
409 SuccessOrExit(error = CopyString(string, size, aArgs[1].GetCString()));
410
411 string = otSrpClientBuffersGetServiceEntryServiceNameString(entry, &size);
412 SuccessOrExit(error = CopyString(string, size, aArgs[2].GetCString()));
413
414 // Service subtypes are added as part of service name as a comma separated list
415 // e.g., "_service._udp,_sub1,_sub2"
416
417 label = strchr(string, ',');
418
419 if (label != nullptr)
420 {
421 uint16_t arrayLength;
422 const char **subTypeLabels = otSrpClientBuffersGetSubTypeLabelsArray(entry, &arrayLength);
423
424 // Leave the last array element as `nullptr` to indicate end of array.
425 for (uint16_t index = 0; index + 1 < arrayLength; index++)
426 {
427 *label++ = '\0';
428 subTypeLabels[index] = label;
429
430 label = strchr(label, ',');
431
432 if (label == nullptr)
433 {
434 break;
435 }
436 }
437
438 VerifyOrExit(label == nullptr, error = OT_ERROR_NO_BUFS);
439 }
440
441 SuccessOrExit(error = aArgs[3].ParseAsUint16(entry->mService.mPort));
442
443 if (!aArgs[4].IsEmpty())
444 {
445 SuccessOrExit(error = aArgs[4].ParseAsUint16(entry->mService.mPriority));
446 }
447
448 if (!aArgs[5].IsEmpty())
449 {
450 SuccessOrExit(error = aArgs[5].ParseAsUint16(entry->mService.mWeight));
451 }
452
453 if (!aArgs[6].IsEmpty())
454 {
455 uint8_t *txtBuffer;
456
457 txtBuffer = otSrpClientBuffersGetServiceEntryTxtBuffer(entry, &size);
458 entry->mTxtEntry.mValueLength = size;
459
460 SuccessOrExit(error = aArgs[6].ParseAsHexString(entry->mTxtEntry.mValueLength, txtBuffer));
461 VerifyOrExit(aArgs[7].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
462 }
463 else
464 {
465 entry->mService.mNumTxtEntries = 0;
466 }
467
468 SuccessOrExit(error = otSrpClientAddService(mInterpreter.mInstance, &entry->mService));
469
470 entry = nullptr;
471
472 exit:
473 if (entry != nullptr)
474 {
475 otSrpClientBuffersFreeService(mInterpreter.mInstance, entry);
476 }
477
478 return error;
479 }
480
OutputHostInfo(uint8_t aIndentSize,const otSrpClientHostInfo & aHostInfo)481 void SrpClient::OutputHostInfo(uint8_t aIndentSize, const otSrpClientHostInfo &aHostInfo)
482 {
483 mInterpreter.OutputFormat(aIndentSize, "name:");
484
485 if (aHostInfo.mName != nullptr)
486 {
487 mInterpreter.OutputFormat("\"%s\"", aHostInfo.mName);
488 }
489 else
490 {
491 mInterpreter.OutputFormat("(null)");
492 }
493
494 mInterpreter.OutputFormat(", state:%s, addrs:[", otSrpClientItemStateToString(aHostInfo.mState));
495
496 for (uint8_t index = 0; index < aHostInfo.mNumAddresses; index++)
497 {
498 if (index > 0)
499 {
500 mInterpreter.OutputFormat(", ");
501 }
502
503 mInterpreter.OutputIp6Address(aHostInfo.mAddresses[index]);
504 }
505
506 mInterpreter.OutputLine("]");
507 }
508
OutputServiceList(uint8_t aIndentSize,const otSrpClientService * aServices)509 void SrpClient::OutputServiceList(uint8_t aIndentSize, const otSrpClientService *aServices)
510 {
511 while (aServices != nullptr)
512 {
513 OutputService(aIndentSize, *aServices);
514 aServices = aServices->mNext;
515 }
516 }
517
OutputService(uint8_t aIndentSize,const otSrpClientService & aService)518 void SrpClient::OutputService(uint8_t aIndentSize, const otSrpClientService &aService)
519 {
520 mInterpreter.OutputFormat(aIndentSize, "instance:\"%s\", name:\"%s", aService.mInstanceName, aService.mName);
521
522 if (aService.mSubTypeLabels != nullptr)
523 {
524 for (uint16_t index = 0; aService.mSubTypeLabels[index] != nullptr; index++)
525 {
526 mInterpreter.OutputFormat(",%s", aService.mSubTypeLabels[index]);
527 }
528 }
529
530 mInterpreter.OutputLine("\", state:%s, port:%d, priority:%d, weight:%d",
531 otSrpClientItemStateToString(aService.mState), aService.mPort, aService.mPriority,
532 aService.mWeight);
533 }
534
ProcessStart(Arg aArgs[])535 otError SrpClient::ProcessStart(Arg aArgs[])
536 {
537 otError error = OT_ERROR_NONE;
538 otSockAddr serverSockAddr;
539
540 SuccessOrExit(error = aArgs[0].ParseAsIp6Address(serverSockAddr.mAddress));
541 SuccessOrExit(error = aArgs[1].ParseAsUint16(serverSockAddr.mPort));
542 VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
543
544 error = otSrpClientStart(mInterpreter.mInstance, &serverSockAddr);
545
546 exit:
547 return error;
548 }
549
ProcessState(Arg aArgs[])550 otError SrpClient::ProcessState(Arg aArgs[])
551 {
552 otError error = OT_ERROR_NONE;
553
554 VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
555
556 mInterpreter.OutputEnabledDisabledStatus(otSrpClientIsRunning(mInterpreter.mInstance));
557
558 exit:
559 return error;
560 }
561
ProcessStop(Arg aArgs[])562 otError SrpClient::ProcessStop(Arg aArgs[])
563 {
564 otError error = OT_ERROR_NONE;
565
566 VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
567 otSrpClientStop(mInterpreter.mInstance);
568
569 exit:
570 return error;
571 }
572
HandleCallback(otError aError,const otSrpClientHostInfo * aHostInfo,const otSrpClientService * aServices,const otSrpClientService * aRemovedServices,void * aContext)573 void SrpClient::HandleCallback(otError aError,
574 const otSrpClientHostInfo *aHostInfo,
575 const otSrpClientService * aServices,
576 const otSrpClientService * aRemovedServices,
577 void * aContext)
578 {
579 static_cast<SrpClient *>(aContext)->HandleCallback(aError, aHostInfo, aServices, aRemovedServices);
580 }
581
HandleCallback(otError aError,const otSrpClientHostInfo * aHostInfo,const otSrpClientService * aServices,const otSrpClientService * aRemovedServices)582 void SrpClient::HandleCallback(otError aError,
583 const otSrpClientHostInfo *aHostInfo,
584 const otSrpClientService * aServices,
585 const otSrpClientService * aRemovedServices)
586 {
587 otSrpClientService *next;
588
589 if (mCallbackEnabled)
590 {
591 mInterpreter.OutputLine("SRP client callback - error:%s", otThreadErrorToString(aError));
592 mInterpreter.OutputLine("Host info:");
593 OutputHostInfo(kIndentSize, *aHostInfo);
594
595 mInterpreter.OutputLine("Service list:");
596 OutputServiceList(kIndentSize, aServices);
597
598 if (aRemovedServices != nullptr)
599 {
600 mInterpreter.OutputLine("Removed service list:");
601 OutputServiceList(kIndentSize, aRemovedServices);
602 }
603 }
604
605 // Go through removed services and free all removed services
606
607 for (const otSrpClientService *service = aRemovedServices; service != nullptr; service = next)
608 {
609 next = service->mNext;
610
611 otSrpClientBuffersFreeService(mInterpreter.mInstance, reinterpret_cast<otSrpClientBuffersServiceEntry *>(
612 const_cast<otSrpClientService *>(service)));
613 }
614 }
615
616 } // namespace Cli
617 } // namespace ot
618
619 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
620