• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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