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