• 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 server.
32  */
33 
34 #include "cli_srp_server.hpp"
35 
36 #include <inttypes.h>
37 
38 #include "cli/cli.hpp"
39 #include "common/string.hpp"
40 
41 #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
42 
43 namespace ot {
44 namespace Cli {
45 
46 /**
47  * @cli srp server addrmode (get,set)
48  * @code
49  * srp server addrmode anycast
50  * Done
51  * @endcode
52  * @code
53  * srp server addrmode
54  * anycast
55  * Done
56  * @endcode
57  * @cparam srp server addrmode [@ca{anycast}|@ca{unicast}|@ca{unicast-force-add}]
58  * @par
59  * Gets or sets the address mode used by the SRP server.
60  * @par
61  * The address mode tells the SRP server how to determine its address and port number,
62  * which then get published in the Thread network data.
63  * @sa otSrpServerGetAddressMode
64  * @sa otSrpServerSetAddressMode
65  */
Process(Arg aArgs[])66 template <> otError SrpServer::Process<Cmd("addrmode")>(Arg aArgs[])
67 {
68     otError error = OT_ERROR_INVALID_ARGS;
69 
70     if (aArgs[0].IsEmpty())
71     {
72         switch (otSrpServerGetAddressMode(GetInstancePtr()))
73         {
74         case OT_SRP_SERVER_ADDRESS_MODE_UNICAST:
75             OutputLine("unicast");
76             break;
77 
78         case OT_SRP_SERVER_ADDRESS_MODE_ANYCAST:
79             OutputLine("anycast");
80             break;
81 
82         case OT_SRP_SERVER_ADDRESS_MODE_UNICAST_FORCE_ADD:
83             OutputLine("unicast-force-add");
84             break;
85         }
86 
87         error = OT_ERROR_NONE;
88     }
89     else if (aArgs[0] == "unicast")
90     {
91         error = otSrpServerSetAddressMode(GetInstancePtr(), OT_SRP_SERVER_ADDRESS_MODE_UNICAST);
92     }
93     else if (aArgs[0] == "anycast")
94     {
95         error = otSrpServerSetAddressMode(GetInstancePtr(), OT_SRP_SERVER_ADDRESS_MODE_ANYCAST);
96     }
97     else if (aArgs[0] == "unicast-force-add")
98     {
99         error = otSrpServerSetAddressMode(GetInstancePtr(), OT_SRP_SERVER_ADDRESS_MODE_UNICAST_FORCE_ADD);
100     }
101 
102     return error;
103 }
104 
105 #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
106 
107 /**
108  * @cli srp server auto (enable,disable)
109  * @code
110  * srp server auto enable
111  * Done
112  * @endcode
113  * @code
114  * srp server auto
115  * Enabled
116  * Done
117  * @endcode
118  * @cparam srp server auto [@ca{enable}|@ca{disable}]
119  * @par
120  * Enables or disables the auto-enable mode on the SRP server.
121  * @par
122  * When this mode is enabled, the Border Routing Manager controls if and when
123  * to enable or disable the SRP server.
124  * @par
125  * This command requires that `OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE` be enabled.
126  * @moreinfo{@srp}.
127  * @sa otSrpServerIsAutoEnableMode
128  * @sa otSrpServerSetAutoEnableMode
129  */
Process(Arg aArgs[])130 template <> otError SrpServer::Process<Cmd("auto")>(Arg aArgs[])
131 {
132     return ProcessEnableDisable(aArgs, otSrpServerIsAutoEnableMode, otSrpServerSetAutoEnableMode);
133 }
134 #endif
135 
136 /**
137  * @cli srp server domain (get,set)
138  * @code
139  * srp server domain thread.service.arpa.
140  * Done
141  * @endcode
142  * @code
143  * srp server domain
144  * thread.service.arpa.
145  * Done
146  * @endcode
147  * @cparam srp server domain [@ca{domain-name}]
148  * @par
149  * Gets or sets the domain name of the SRP server.
150  * @sa otSrpServerGetDomain
151  * @sa otSrpServerSetDomain
152  */
Process(Arg aArgs[])153 template <> otError SrpServer::Process<Cmd("domain")>(Arg aArgs[])
154 {
155     otError error = OT_ERROR_NONE;
156 
157     if (aArgs[0].IsEmpty())
158     {
159         OutputLine("%s", otSrpServerGetDomain(GetInstancePtr()));
160     }
161     else
162     {
163         error = otSrpServerSetDomain(GetInstancePtr(), aArgs[0].GetCString());
164     }
165 
166     return error;
167 }
168 
169 #if OPENTHREAD_CONFIG_SRP_SERVER_FAST_START_MODE_ENABLE
170 /**
171  * @cli srp server faststart (enable)
172  * @code
173  * srp server faststart enable
174  * Done
175  * @endcode
176  * @code
177  * srp server faststart
178  * Enabled
179  * Done
180  * @endcode
181  * @cparam srp server faststart [@ca{enable}]
182  * @par
183  * Enables the "Fast Start Mode" on the SRP server.
184  * @par
185  * The Fast Start Mode is designed for scenarios where a device, often a mobile device, needs to act as a provisional
186  * SRP server (e.g., functioning as a temporary Border Router). The SRP server function is enabled only if no other
187  * Border Routers (BRs) are already providing the SRP service within the Thread network. Importantly, Fast Start Mode
188  * allows the device to quickly start its SRP server functionality upon joining the network, allowing other Thread
189  * devices to quickly connect and register their services without the typical delays associated with standard Border
190  * Router initialization (and SRP server startup).
191  * @par
192  * The Fast Start Mode can be enabled when the device is in the detached or disabled state, the SRP server is currently
193  * disabled, and "auto-enable mode" is not in use.
194  * @par
195  * After successfully enabling Fast Start Mode, it can be disabled by a direct command to enable/disable the SRP
196  * server, using `srp server [enable/disable]`.
197  * @par
198  * This command requires that `OPENTHREAD_CONFIG_SRP_SERVER_FAST_START_MODE_ENABLE` be enabled.
199  * @moreinfo{@srp}.
200  * @sa otSrpServerIsFastStartModeEnabled
201  * @sa otSrpServerEnableFastStartMode
202  */
Process(Arg aArgs[])203 template <> otError SrpServer::Process<Cmd("faststart")>(Arg aArgs[])
204 {
205     otError error = OT_ERROR_NONE;
206 
207     if (aArgs[0].IsEmpty())
208     {
209         OutputEnabledDisabledStatus(otSrpServerIsFastStartModeEnabled(GetInstancePtr()));
210     }
211     else if (aArgs[0] == "enable")
212     {
213         error = otSrpServerEnableFastStartMode(GetInstancePtr());
214     }
215     else
216     {
217         error = OT_ERROR_INVALID_ARGS;
218     }
219 
220     return error;
221 }
222 
223 #endif // OPENTHREAD_CONFIG_SRP_SERVER_FAST_START_MODE_ENABLE
224 
225 /**
226  * @cli srp server state
227  * @code
228  * srp server state
229  * running
230  * Done
231  * @endcode
232  * @par
233  * Returns one of the following possible states of the SRP server:
234  *  * `disabled`: The SRP server is not enabled.
235  *  * `stopped`: The SRP server is enabled but not active due to existing
236  *               SRP servers that are already active in the Thread network.
237  *               The SRP server may become active when the existing
238  *               SRP servers are no longer active within the Thread network.
239  *  * `running`: The SRP server is active and can handle service registrations.
240  * @par
241  * @moreinfo{@srp}.
242  * @sa otSrpServerGetState
243  */
Process(Arg aArgs[])244 template <> otError SrpServer::Process<Cmd("state")>(Arg aArgs[])
245 {
246     static const char *const kStateStrings[] = {
247         "disabled", // (0) OT_SRP_SERVER_STATE_DISABLED
248         "running",  // (1) OT_SRP_SERVER_STATE_RUNNING
249         "stopped",  // (2) OT_SRP_SERVER_STATE_STOPPED
250     };
251 
252     OT_UNUSED_VARIABLE(aArgs);
253 
254     static_assert(0 == OT_SRP_SERVER_STATE_DISABLED, "OT_SRP_SERVER_STATE_DISABLED value is incorrect");
255     static_assert(1 == OT_SRP_SERVER_STATE_RUNNING, "OT_SRP_SERVER_STATE_RUNNING value is incorrect");
256     static_assert(2 == OT_SRP_SERVER_STATE_STOPPED, "OT_SRP_SERVER_STATE_STOPPED value is incorrect");
257 
258     OutputLine("%s", Stringify(otSrpServerGetState(GetInstancePtr()), kStateStrings));
259 
260     return OT_ERROR_NONE;
261 }
262 
Process(Arg aArgs[])263 template <> otError SrpServer::Process<Cmd("enable")>(Arg aArgs[])
264 {
265     OT_UNUSED_VARIABLE(aArgs);
266 
267     otSrpServerSetEnabled(GetInstancePtr(), /* aEnabled */ true);
268 
269     return OT_ERROR_NONE;
270 }
271 
272 /**
273  * @cli srp server (enable,disable)
274  * @code
275  * srp server disable
276  * Done
277  * @endcode
278  * @cparam srp server [@ca{enable}|@ca{disable}]
279  * @par
280  * Enables or disables the SRP server. @moreinfo{@srp}.
281  * @sa otSrpServerSetEnabled
282  */
Process(Arg aArgs[])283 template <> otError SrpServer::Process<Cmd("disable")>(Arg aArgs[])
284 {
285     OT_UNUSED_VARIABLE(aArgs);
286 
287     otSrpServerSetEnabled(GetInstancePtr(), /* aEnabled */ false);
288 
289     return OT_ERROR_NONE;
290 }
291 
Process(Arg aArgs[])292 template <> otError SrpServer::Process<Cmd("ttl")>(Arg aArgs[])
293 {
294     otError              error = OT_ERROR_NONE;
295     otSrpServerTtlConfig ttlConfig;
296 
297     if (aArgs[0].IsEmpty())
298     {
299         otSrpServerGetTtlConfig(GetInstancePtr(), &ttlConfig);
300         OutputLine("min ttl: %lu", ToUlong(ttlConfig.mMinTtl));
301         OutputLine("max ttl: %lu", ToUlong(ttlConfig.mMaxTtl));
302     }
303     else
304     {
305         SuccessOrExit(error = aArgs[0].ParseAsUint32(ttlConfig.mMinTtl));
306         SuccessOrExit(error = aArgs[1].ParseAsUint32(ttlConfig.mMaxTtl));
307         VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
308 
309         error = otSrpServerSetTtlConfig(GetInstancePtr(), &ttlConfig);
310     }
311 
312 exit:
313     return error;
314 }
315 
316 /**
317  * @cli srp server lease (get,set)
318  * @code
319  * srp server lease 1800 7200 86400 1209600
320  * Done
321  * @endcode
322  * @code
323  * srp server lease
324  * min lease: 1800
325  * max lease: 7200
326  * min key-lease: 86400
327  * max key-lease: 1209600
328  * Done
329  * @endcode
330  * @cparam srp server lease [@ca{min-lease} @ca{max-lease} @ca{min-key-lease} @ca{max-key-lease}]
331  * @par
332  * Gets or sets the SRP server lease values in number of seconds.
333  * @sa otSrpServerGetLeaseConfig
334  * @sa otSrpServerSetLeaseConfig
335  */
Process(Arg aArgs[])336 template <> otError SrpServer::Process<Cmd("lease")>(Arg aArgs[])
337 {
338     otError                error = OT_ERROR_NONE;
339     otSrpServerLeaseConfig leaseConfig;
340 
341     if (aArgs[0].IsEmpty())
342     {
343         otSrpServerGetLeaseConfig(GetInstancePtr(), &leaseConfig);
344         OutputLine("min lease: %lu", ToUlong(leaseConfig.mMinLease));
345         OutputLine("max lease: %lu", ToUlong(leaseConfig.mMaxLease));
346         OutputLine("min key-lease: %lu", ToUlong(leaseConfig.mMinKeyLease));
347         OutputLine("max key-lease: %lu", ToUlong(leaseConfig.mMaxKeyLease));
348     }
349     else
350     {
351         SuccessOrExit(error = aArgs[0].ParseAsUint32(leaseConfig.mMinLease));
352         SuccessOrExit(error = aArgs[1].ParseAsUint32(leaseConfig.mMaxLease));
353         SuccessOrExit(error = aArgs[2].ParseAsUint32(leaseConfig.mMinKeyLease));
354         SuccessOrExit(error = aArgs[3].ParseAsUint32(leaseConfig.mMaxKeyLease));
355         VerifyOrExit(aArgs[4].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
356 
357         error = otSrpServerSetLeaseConfig(GetInstancePtr(), &leaseConfig);
358     }
359 
360 exit:
361     return error;
362 }
363 
364 /**
365  * @cli srp server host
366  * @code
367  * srp server host
368  * srp-api-test-1.default.service.arpa.
369  *     deleted: false
370  *     addresses: [fdde:ad00:beef:0:0:ff:fe00:fc10]
371  * srp-api-test-0.default.service.arpa.
372  *     deleted: false
373  *     addresses: [fdde:ad00:beef:0:0:ff:fe00:fc10]
374  * Done
375  * @endcode
376  * @par
377  * Returns information about all registered hosts. @moreinfo{@srp}.
378  * @sa otSrpServerGetNextHost
379  * @sa otSrpServerHostGetAddresses
380  * @sa otSrpServerHostGetFullName
381  */
Process(Arg aArgs[])382 template <> otError SrpServer::Process<Cmd("host")>(Arg aArgs[])
383 {
384     otError                error = OT_ERROR_NONE;
385     const otSrpServerHost *host;
386 
387     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
388 
389     host = nullptr;
390     while ((host = otSrpServerGetNextHost(GetInstancePtr(), host)) != nullptr)
391     {
392         const otIp6Address *addresses;
393         uint8_t             addressesNum;
394         bool                isDeleted = otSrpServerHostIsDeleted(host);
395 
396         OutputLine("%s", otSrpServerHostGetFullName(host));
397         OutputLine(kIndentSize, "deleted: %s", isDeleted ? "true" : "false");
398         if (isDeleted)
399         {
400             continue;
401         }
402 
403         OutputSpaces(kIndentSize);
404         OutputFormat("addresses: [");
405 
406         addresses = otSrpServerHostGetAddresses(host, &addressesNum);
407 
408         for (uint8_t i = 0; i < addressesNum; ++i)
409         {
410             OutputIp6Address(addresses[i]);
411             if (i < addressesNum - 1)
412             {
413                 OutputFormat(", ");
414             }
415         }
416 
417         OutputLine("]");
418     }
419 
420 exit:
421     return error;
422 }
423 
OutputHostAddresses(const otSrpServerHost * aHost)424 void SrpServer::OutputHostAddresses(const otSrpServerHost *aHost)
425 {
426     const otIp6Address *addresses;
427     uint8_t             addressesNum;
428 
429     addresses = otSrpServerHostGetAddresses(aHost, &addressesNum);
430 
431     OutputFormat("[");
432     for (uint8_t i = 0; i < addressesNum; ++i)
433     {
434         if (i != 0)
435         {
436             OutputFormat(", ");
437         }
438 
439         OutputIp6Address(addresses[i]);
440     }
441     OutputFormat("]");
442 }
443 
444 /**
445  * @cli srp server service
446  * @code
447  * srp server service
448  * srp-api-test-1._ipps._tcp.default.service.arpa.
449  *     deleted: false
450  *     subtypes: (null)
451  *     port: 49152
452  *     priority: 0
453  *     weight: 0
454  *     ttl: 7200
455  *     lease: 7200
456  *     key-lease: 1209600
457  *     TXT: [616263, xyz=585960]
458  *     host: srp-api-test-1.default.service.arpa.
459  *     addresses: [fdde:ad00:beef:0:0:ff:fe00:fc10]
460  * srp-api-test-0._ipps._tcp.default.service.arpa.
461  *     deleted: false
462  *     subtypes: _sub1,_sub2
463  *     port: 49152
464  *     priority: 0
465  *     weight: 0
466  *     ttl: 3600
467  *     lease: 3600
468  *     key-lease: 1209600
469  *     TXT: [616263, xyz=585960]
470  *     host: srp-api-test-0.default.service.arpa.
471  *     addresses: [fdde:ad00:beef:0:0:ff:fe00:fc10]
472  * Done
473  * @endcode
474  * @par
475  * Returns information about registered services.
476  * @par
477  * The `TXT` record is displayed
478  * as an array of entries. If an entry contains a key, the key is printed in
479  * ASCII format. The value portion is printed in hexadecimal bytes.
480  * @moreinfo{@srp}.
481  * @sa otSrpServerServiceGetInstanceName
482  * @sa otSrpServerServiceGetServiceName
483  * @sa otSrpServerServiceGetSubTypeServiceNameAt
484  */
Process(Arg aArgs[])485 template <> otError SrpServer::Process<Cmd("service")>(Arg aArgs[])
486 {
487     otError                error = OT_ERROR_NONE;
488     const otSrpServerHost *host  = nullptr;
489 
490     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
491 
492     while ((host = otSrpServerGetNextHost(GetInstancePtr(), host)) != nullptr)
493     {
494         const otSrpServerService *service = nullptr;
495 
496         while ((service = otSrpServerHostGetNextService(host, service)) != nullptr)
497         {
498             bool                 isDeleted = otSrpServerServiceIsDeleted(service);
499             const uint8_t       *txtData;
500             uint16_t             txtDataLength;
501             bool                 hasSubType = false;
502             otSrpServerLeaseInfo leaseInfo;
503 
504             OutputLine("%s", otSrpServerServiceGetInstanceName(service));
505             OutputLine(kIndentSize, "deleted: %s", isDeleted ? "true" : "false");
506 
507             if (isDeleted)
508             {
509                 continue;
510             }
511 
512             otSrpServerServiceGetLeaseInfo(service, &leaseInfo);
513 
514             OutputFormat(kIndentSize, "subtypes: ");
515 
516             for (uint16_t index = 0;; index++)
517             {
518                 char        subLabel[OT_DNS_MAX_LABEL_SIZE];
519                 const char *subTypeName = otSrpServerServiceGetSubTypeServiceNameAt(service, index);
520 
521                 if (subTypeName == nullptr)
522                 {
523                     break;
524                 }
525 
526                 IgnoreError(otSrpServerParseSubTypeServiceName(subTypeName, subLabel, sizeof(subLabel)));
527                 OutputFormat("%s%s", hasSubType ? "," : "", subLabel);
528                 hasSubType = true;
529             }
530 
531             OutputLine(hasSubType ? "" : "(null)");
532 
533             OutputLine(kIndentSize, "port: %u", otSrpServerServiceGetPort(service));
534             OutputLine(kIndentSize, "priority: %u", otSrpServerServiceGetPriority(service));
535             OutputLine(kIndentSize, "weight: %u", otSrpServerServiceGetWeight(service));
536             OutputLine(kIndentSize, "ttl: %lu", ToUlong(otSrpServerServiceGetTtl(service)));
537             OutputLine(kIndentSize, "lease: %lu", ToUlong(leaseInfo.mLease / 1000));
538             OutputLine(kIndentSize, "key-lease: %lu", ToUlong(leaseInfo.mKeyLease / 1000));
539 
540             txtData = otSrpServerServiceGetTxtData(service, &txtDataLength);
541             OutputFormat(kIndentSize, "TXT: ");
542             OutputDnsTxtData(txtData, txtDataLength);
543             OutputNewLine();
544 
545             OutputLine(kIndentSize, "host: %s", otSrpServerHostGetFullName(host));
546 
547             OutputFormat(kIndentSize, "addresses: ");
548             OutputHostAddresses(host);
549             OutputNewLine();
550         }
551     }
552 
553 exit:
554     return error;
555 }
556 
557 /**
558  * @cli srp server seqnum (get,set)
559  * @code
560  * srp server seqnum 20
561  * Done
562  * @endcode
563  * @code
564  * srp server seqnum
565  * 20
566  * Done
567  * @endcode
568  * @cparam srp server seqnum [@ca{seqnum}]
569  * @par
570  * Gets or sets the sequence number used with the anycast address mode.
571  * The sequence number is included in the "DNS/SRP Service Anycast Address"
572  * entry that is published in the Network Data.
573  * @sa otSrpServerGetAnycastModeSequenceNumber
574  * @sa otSrpServerSetAnycastModeSequenceNumber
575  */
Process(Arg aArgs[])576 template <> otError SrpServer::Process<Cmd("seqnum")>(Arg aArgs[])
577 {
578     otError error = OT_ERROR_NONE;
579 
580     if (aArgs[0].IsEmpty())
581     {
582         OutputLine("%u", otSrpServerGetAnycastModeSequenceNumber(GetInstancePtr()));
583     }
584     else
585     {
586         uint8_t sequenceNumber;
587 
588         SuccessOrExit(error = aArgs[0].ParseAsUint8(sequenceNumber));
589         error = otSrpServerSetAnycastModeSequenceNumber(GetInstancePtr(), sequenceNumber);
590     }
591 
592 exit:
593     return error;
594 }
595 
Process(Arg aArgs[])596 otError SrpServer::Process(Arg aArgs[])
597 {
598 #define CmdEntry(aCommandString)                                 \
599     {                                                            \
600         aCommandString, &SrpServer::Process<Cmd(aCommandString)> \
601     }
602 
603     static constexpr Command kCommands[] = {
604         CmdEntry("addrmode"),
605 #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
606         CmdEntry("auto"),
607 #endif
608         CmdEntry("disable"),
609         CmdEntry("domain"),
610         CmdEntry("enable"),
611 #if OPENTHREAD_CONFIG_SRP_SERVER_FAST_START_MODE_ENABLE
612         CmdEntry("faststart"),
613 #endif
614         CmdEntry("host"),
615         CmdEntry("lease"),
616         CmdEntry("seqnum"),
617         CmdEntry("service"),
618         CmdEntry("state"),
619         CmdEntry("ttl"),
620     };
621 
622     static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
623 
624     otError        error = OT_ERROR_INVALID_COMMAND;
625     const Command *command;
626 
627     if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
628     {
629         OutputCommandTable(kCommands);
630         ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
631     }
632 
633     command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
634     VerifyOrExit(command != nullptr);
635 
636     error = (this->*command->mHandler)(aArgs + 1);
637 
638 exit:
639     return error;
640 }
641 
642 } // namespace Cli
643 } // namespace ot
644 
645 #endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
646