1 /*
2  *  Copyright (c) 2023, 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 CLI for Border Router.
32  */
33 
34 #include "cli_br.hpp"
35 
36 #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
37 
38 #include <string.h>
39 
40 #include "cli/cli.hpp"
41 
42 namespace ot {
43 namespace Cli {
44 
45 /**
46  * @cli br init
47  * @code
48  * br init 2 1
49  * Done
50  * @endcode
51  * @cparam br init @ca{infrastructure-network-index} @ca{is-running}
52  * @par
53  * Initializes the Border Routing Manager.
54  * @sa otBorderRoutingInit
55  */
Process(Arg aArgs[])56 template <> otError Br::Process<Cmd("init")>(Arg aArgs[])
57 {
58     otError  error = OT_ERROR_NONE;
59     uint32_t ifIndex;
60     bool     isRunning;
61 
62     SuccessOrExit(error = aArgs[0].ParseAsUint32(ifIndex));
63     SuccessOrExit(error = aArgs[1].ParseAsBool(isRunning));
64     VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
65     error = otBorderRoutingInit(GetInstancePtr(), ifIndex, isRunning);
66 
67 exit:
68     return error;
69 }
70 
71 /**
72  * @cli br enable
73  * @code
74  * br enable
75  * Done
76  * @endcode
77  * @par
78  * Enables the Border Routing Manager.
79  * @sa otBorderRoutingSetEnabled
80  */
Process(Arg aArgs[])81 template <> otError Br::Process<Cmd("enable")>(Arg aArgs[])
82 {
83     otError error = OT_ERROR_NONE;
84 
85     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
86     error = otBorderRoutingSetEnabled(GetInstancePtr(), true);
87 
88 exit:
89     return error;
90 }
91 
92 /**
93  * @cli br disable
94  * @code
95  * br disable
96  * Done
97  * @endcode
98  * @par
99  * Disables the Border Routing Manager.
100  * @sa otBorderRoutingSetEnabled
101  */
Process(Arg aArgs[])102 template <> otError Br::Process<Cmd("disable")>(Arg aArgs[])
103 {
104     otError error = OT_ERROR_NONE;
105 
106     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
107     error = otBorderRoutingSetEnabled(GetInstancePtr(), false);
108 
109 exit:
110     return error;
111 }
112 
113 /**
114  * @cli br state
115  * @code
116  * br state
117  * running
118  * @endcode
119  * @par api_copy
120  * #otBorderRoutingGetState
121  */
Process(Arg aArgs[])122 template <> otError Br::Process<Cmd("state")>(Arg aArgs[])
123 {
124     static const char *const kStateStrings[] = {
125 
126         "uninitialized", // (0) OT_BORDER_ROUTING_STATE_UNINITIALIZED
127         "disabled",      // (1) OT_BORDER_ROUTING_STATE_DISABLED
128         "stopped",       // (2) OT_BORDER_ROUTING_STATE_STOPPED
129         "running",       // (3) OT_BORDER_ROUTING_STATE_RUNNING
130     };
131 
132     otError error = OT_ERROR_NONE;
133 
134     static_assert(0 == OT_BORDER_ROUTING_STATE_UNINITIALIZED, "STATE_UNINITIALIZED value is incorrect");
135     static_assert(1 == OT_BORDER_ROUTING_STATE_DISABLED, "STATE_DISABLED value is incorrect");
136     static_assert(2 == OT_BORDER_ROUTING_STATE_STOPPED, "STATE_STOPPED value is incorrect");
137     static_assert(3 == OT_BORDER_ROUTING_STATE_RUNNING, "STATE_RUNNING value is incorrect");
138 
139     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
140     OutputLine("%s", Stringify(otBorderRoutingGetState(GetInstancePtr()), kStateStrings));
141 
142 exit:
143     return error;
144 }
145 
ParsePrefixTypeArgs(Arg aArgs[],PrefixType & aFlags)146 otError Br::ParsePrefixTypeArgs(Arg aArgs[], PrefixType &aFlags)
147 {
148     otError error = OT_ERROR_NONE;
149 
150     aFlags = 0;
151 
152     if (aArgs[0].IsEmpty())
153     {
154         aFlags = kPrefixTypeFavored | kPrefixTypeLocal;
155         ExitNow();
156     }
157 
158     if (aArgs[0] == "local")
159     {
160         aFlags = kPrefixTypeLocal;
161     }
162     else if (aArgs[0] == "favored")
163     {
164         aFlags = kPrefixTypeFavored;
165     }
166     else
167     {
168         ExitNow(error = OT_ERROR_INVALID_ARGS);
169     }
170 
171     VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
172 
173 exit:
174     return error;
175 }
176 
177 /**
178  * @cli br omrprefix
179  * @code
180  * br omrprefix
181  * Local: fdfc:1ff5:1512:5622::/64
182  * Favored: fdfc:1ff5:1512:5622::/64 prf:low
183  * Done
184  * @endcode
185  * @par
186  * Outputs both local and favored OMR prefix.
187  * @sa otBorderRoutingGetOmrPrefix
188  * @sa otBorderRoutingGetFavoredOmrPrefix
189  */
Process(Arg aArgs[])190 template <> otError Br::Process<Cmd("omrprefix")>(Arg aArgs[])
191 {
192     otError    error = OT_ERROR_NONE;
193     PrefixType outputPrefixTypes;
194 
195     SuccessOrExit(error = ParsePrefixTypeArgs(aArgs, outputPrefixTypes));
196 
197     /**
198      * @cli br omrprefix local
199      * @code
200      * br omrprefix local
201      * fdfc:1ff5:1512:5622::/64
202      * Done
203      * @endcode
204      * @par api_copy
205      * #otBorderRoutingGetOmrPrefix
206      */
207     if (outputPrefixTypes & kPrefixTypeLocal)
208     {
209         otIp6Prefix local;
210 
211         SuccessOrExit(error = otBorderRoutingGetOmrPrefix(GetInstancePtr(), &local));
212 
213         OutputFormat("%s", outputPrefixTypes == kPrefixTypeLocal ? "" : "Local: ");
214         OutputIp6PrefixLine(local);
215     }
216 
217     /**
218      * @cli br omrprefix favored
219      * @code
220      * br omrprefix favored
221      * fdfc:1ff5:1512:5622::/64 prf:low
222      * Done
223      * @endcode
224      * @par api_copy
225      * #otBorderRoutingGetFavoredOmrPrefix
226      */
227     if (outputPrefixTypes & kPrefixTypeFavored)
228     {
229         otIp6Prefix       favored;
230         otRoutePreference preference;
231 
232         SuccessOrExit(error = otBorderRoutingGetFavoredOmrPrefix(GetInstancePtr(), &favored, &preference));
233 
234         OutputFormat("%s", outputPrefixTypes == kPrefixTypeFavored ? "" : "Favored: ");
235         OutputIp6Prefix(favored);
236         OutputLine(" prf:%s", PreferenceToString(preference));
237     }
238 
239 exit:
240     return error;
241 }
242 
243 /**
244  * @cli br onlinkprefix
245  * @code
246  * br onlinkprefix
247  * Local: fd41:2650:a6f5:0::/64
248  * Favored: 2600::0:1234:da12::/64
249  * Done
250  * @endcode
251  * @par
252  * Outputs both local and favored on-link prefixes.
253  * @sa otBorderRoutingGetOnLinkPrefix
254  * @sa otBorderRoutingGetFavoredOnLinkPrefix
255  */
Process(Arg aArgs[])256 template <> otError Br::Process<Cmd("onlinkprefix")>(Arg aArgs[])
257 {
258     otError    error = OT_ERROR_NONE;
259     PrefixType outputPrefixTypes;
260 
261     SuccessOrExit(error = ParsePrefixTypeArgs(aArgs, outputPrefixTypes));
262 
263     /**
264      * @cli br onlinkprefix local
265      * @code
266      * br onlinkprefix local
267      * fd41:2650:a6f5:0::/64
268      * Done
269      * @endcode
270      * @par api_copy
271      * #otBorderRoutingGetOnLinkPrefix
272      */
273     if (outputPrefixTypes & kPrefixTypeLocal)
274     {
275         otIp6Prefix local;
276 
277         SuccessOrExit(error = otBorderRoutingGetOnLinkPrefix(GetInstancePtr(), &local));
278 
279         OutputFormat("%s", outputPrefixTypes == kPrefixTypeLocal ? "" : "Local: ");
280         OutputIp6PrefixLine(local);
281     }
282 
283     /**
284      * @cli br onlinkprefix favored
285      * @code
286      * br onlinkprefix favored
287      * 2600::0:1234:da12::/64
288      * Done
289      * @endcode
290      * @par api_copy
291      * #otBorderRoutingGetFavoredOnLinkPrefix
292      */
293     if (outputPrefixTypes & kPrefixTypeFavored)
294     {
295         otIp6Prefix favored;
296 
297         SuccessOrExit(error = otBorderRoutingGetFavoredOnLinkPrefix(GetInstancePtr(), &favored));
298 
299         OutputFormat("%s", outputPrefixTypes == kPrefixTypeFavored ? "" : "Favored: ");
300         OutputIp6PrefixLine(favored);
301     }
302 
303 exit:
304     return error;
305 }
306 
307 #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE
308 
309 /**
310  * @cli br nat64prefix
311  * @code
312  * br nat64prefix
313  * Local: fd14:1078:b3d5:b0b0:0:0::/96
314  * Favored: fd14:1078:b3d5:b0b0:0:0::/96 prf:low
315  * Done
316  * @endcode
317  * @par
318  * Outputs both local and favored NAT64 prefixes.
319  * @sa otBorderRoutingGetNat64Prefix
320  * @sa otBorderRoutingGetFavoredNat64Prefix
321  */
Process(Arg aArgs[])322 template <> otError Br::Process<Cmd("nat64prefix")>(Arg aArgs[])
323 {
324     otError    error = OT_ERROR_NONE;
325     PrefixType outputPrefixTypes;
326 
327     SuccessOrExit(error = ParsePrefixTypeArgs(aArgs, outputPrefixTypes));
328 
329     /**
330      * @cli br nat64prefix local
331      * @code
332      * br nat64prefix local
333      * fd14:1078:b3d5:b0b0:0:0::/96
334      * Done
335      * @endcode
336      * @par api_copy
337      * #otBorderRoutingGetNat64Prefix
338      */
339     if (outputPrefixTypes & kPrefixTypeLocal)
340     {
341         otIp6Prefix local;
342 
343         SuccessOrExit(error = otBorderRoutingGetNat64Prefix(GetInstancePtr(), &local));
344 
345         OutputFormat("%s", outputPrefixTypes == kPrefixTypeLocal ? "" : "Local: ");
346         OutputIp6PrefixLine(local);
347     }
348 
349     /**
350      * @cli br nat64prefix favored
351      * @code
352      * br nat64prefix favored
353      * fd14:1078:b3d5:b0b0:0:0::/96 prf:low
354      * Done
355      * @endcode
356      * @par api_copy
357      * #otBorderRoutingGetFavoredNat64Prefix
358      */
359     if (outputPrefixTypes & kPrefixTypeFavored)
360     {
361         otIp6Prefix       favored;
362         otRoutePreference preference;
363 
364         SuccessOrExit(error = otBorderRoutingGetFavoredNat64Prefix(GetInstancePtr(), &favored, &preference));
365 
366         OutputFormat("%s", outputPrefixTypes == kPrefixTypeFavored ? "" : "Favored: ");
367         OutputIp6Prefix(favored);
368         OutputLine(" prf:%s", PreferenceToString(preference));
369     }
370 
371 exit:
372     return error;
373 }
374 
375 #endif // OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE
376 
377 /**
378  * @cli br prefixtable
379  * @code
380  * br prefixtable
381  * prefix:fd00:1234:5678:0::/64, on-link:no, ms-since-rx:29526, lifetime:1800, route-prf:med,
382  * router:ff02:0:0:0:0:0:0:1 (M:0 O:0 Stub:1)
383  * prefix:1200:abba:baba:0::/64, on-link:yes, ms-since-rx:29527, lifetime:1800, preferred:1800,
384  * router:ff02:0:0:0:0:0:0:1 (M:0 O:0 Stub:1)
385  * Done
386  * @endcode
387  * @par
388  * Get the discovered prefixes by Border Routing Manager on the infrastructure link.
389  * Info per prefix entry:
390  * - The prefix
391  * - Whether the prefix is on-link or route
392  * - Milliseconds since last received Router Advertisement containing this prefix
393  * - Prefix lifetime in seconds
394  * - Preferred lifetime in seconds only if prefix is on-link
395  * - Route preference (low, med, high) only if prefix is route (not on-link)
396  * - The router IPv6 address which advertising this prefix
397  * - Flags in received Router Advertisement header:
398  *   - M: Managed Address Config flag
399  *   - O: Other Config flag
400  *   - Stub: Stub Router flag (indicates whether the router is a stub router)
401  * @sa otBorderRoutingGetNextPrefixTableEntry
402  */
Process(Arg aArgs[])403 template <> otError Br::Process<Cmd("prefixtable")>(Arg aArgs[])
404 {
405     otError                            error = OT_ERROR_NONE;
406     otBorderRoutingPrefixTableIterator iterator;
407     otBorderRoutingPrefixTableEntry    entry;
408 
409     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
410 
411     otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator);
412 
413     while (otBorderRoutingGetNextPrefixTableEntry(GetInstancePtr(), &iterator, &entry) == OT_ERROR_NONE)
414     {
415         char string[OT_IP6_PREFIX_STRING_SIZE];
416 
417         otIp6PrefixToString(&entry.mPrefix, string, sizeof(string));
418         OutputFormat("prefix:%s, on-link:%s, ms-since-rx:%lu, lifetime:%lu, ", string, entry.mIsOnLink ? "yes" : "no",
419                      ToUlong(entry.mMsecSinceLastUpdate), ToUlong(entry.mValidLifetime));
420 
421         if (entry.mIsOnLink)
422         {
423             OutputFormat("preferred:%lu, ", ToUlong(entry.mPreferredLifetime));
424         }
425         else
426         {
427             OutputFormat("route-prf:%s, ", PreferenceToString(entry.mRoutePreference));
428         }
429 
430         OutputFormat("router:");
431         OutputRouterInfo(entry.mRouter, kShortVersion);
432     }
433 
434 exit:
435     return error;
436 }
437 
438 #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
Process(Arg aArgs[])439 template <> otError Br::Process<Cmd("pd")>(Arg aArgs[])
440 {
441     otError error = OT_ERROR_NONE;
442 
443     /**
444      * @cli br pd (enable,disable)
445      * @code
446      * br pd enable
447      * Done
448      * @endcode
449      * @code
450      * br pd disable
451      * Done
452      * @endcode
453      * @cparam br pd @ca{enable|disable}
454      * @par api_copy
455      * #otBorderRoutingDhcp6PdSetEnabled
456      *
457      */
458     if (ProcessEnableDisable(aArgs, otBorderRoutingDhcp6PdSetEnabled) == OT_ERROR_NONE)
459     {
460     }
461     /**
462      * @cli br pd state
463      * @code
464      * br pd state
465      * running
466      * Done
467      * @endcode
468      * @par api_copy
469      * #otBorderRoutingDhcp6PdGetState
470      */
471     else if (aArgs[0] == "state")
472     {
473         static const char *const kDhcpv6PdStateStrings[] = {
474             "disabled", // (0) OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED
475             "stopped",  // (1) OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED
476             "running",  // (2) OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING
477         };
478 
479         static_assert(0 == OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED,
480                       "OT_BORDER_ROUTING_DHCP6_PD_STATE_DISABLED value is not expected!");
481         static_assert(1 == OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED,
482                       "OT_BORDER_ROUTING_DHCP6_PD_STATE_STOPPED value is not expected!");
483         static_assert(2 == OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING,
484                       "OT_BORDER_ROUTING_DHCP6_PD_STATE_RUNNING value is not expected!");
485 
486         OutputLine("%s", Stringify(otBorderRoutingDhcp6PdGetState(GetInstancePtr()), kDhcpv6PdStateStrings));
487     }
488     /**
489      * @cli br pd omrprefix
490      * @code
491      * br pd omrprefix
492      * 2001:db8:cafe:0:0/64 lifetime:1800 preferred:1800
493      * Done
494      * @endcode
495      * @par api_copy
496      * #otBorderRoutingGetPdOmrPrefix
497      */
498     else if (aArgs[0] == "omrprefix")
499     {
500         otBorderRoutingPrefixTableEntry entry;
501 
502         SuccessOrExit(error = otBorderRoutingGetPdOmrPrefix(GetInstancePtr(), &entry));
503 
504         OutputIp6Prefix(entry.mPrefix);
505         OutputLine(" lifetime:%lu preferred:%lu", ToUlong(entry.mValidLifetime), ToUlong(entry.mPreferredLifetime));
506     }
507     else
508     {
509         ExitNow(error = OT_ERROR_INVALID_COMMAND);
510     }
511 
512 exit:
513     return error;
514 }
515 #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
516 
517 /**
518  * @cli br routers
519  * @code
520  * br routers
521  * ff02:0:0:0:0:0:0:1 (M:0 O:0 Stub:1) ms-since-rx:1505
522  * Done
523  * @endcode
524  * @par
525  * Get the list of discovered routers by Border Routing Manager on the infrastructure link.
526  * Info per router:
527  * - The router IPv6 address
528  * - Flags in received Router Advertisement header:
529  *   - M: Managed Address Config flag
530  *   - O: Other Config flag
531  *   - Stub: Stub Router flag (indicates whether the router is a stub router)
532  * - Milliseconds since last received message from this router
533  * @sa otBorderRoutingGetNextRouterEntry
534  */
Process(Arg aArgs[])535 template <> otError Br::Process<Cmd("routers")>(Arg aArgs[])
536 {
537     otError                            error = OT_ERROR_NONE;
538     otBorderRoutingPrefixTableIterator iterator;
539     otBorderRoutingRouterEntry         entry;
540 
541     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
542 
543     otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator);
544 
545     while (otBorderRoutingGetNextRouterEntry(GetInstancePtr(), &iterator, &entry) == OT_ERROR_NONE)
546     {
547         OutputRouterInfo(entry, kLongVersion);
548     }
549 
550 exit:
551     return error;
552 }
553 
OutputRouterInfo(const otBorderRoutingRouterEntry & aEntry,RouterOutputMode aMode)554 void Br::OutputRouterInfo(const otBorderRoutingRouterEntry &aEntry, RouterOutputMode aMode)
555 {
556     OutputIp6Address(aEntry.mAddress);
557     OutputFormat(" (M:%u O:%u Stub:%u)", aEntry.mManagedAddressConfigFlag, aEntry.mOtherConfigFlag,
558                  aEntry.mStubRouterFlag);
559 
560     if (aMode == kLongVersion)
561     {
562         OutputFormat(" ms-since-rx:%lu", ToUlong(aEntry.mMsecSinceLastUpdate));
563 
564         if (aEntry.mIsLocalDevice)
565         {
566             OutputFormat(" (this BR)");
567         }
568     }
569 
570     OutputNewLine();
571 }
572 
Process(Arg aArgs[])573 template <> otError Br::Process<Cmd("raoptions")>(Arg aArgs[])
574 {
575     static constexpr uint16_t kMaxExtraOptions = 800;
576 
577     otError  error = OT_ERROR_NONE;
578     uint8_t  options[kMaxExtraOptions];
579     uint16_t length;
580 
581     /**
582      * @cli br raoptions (set,clear)
583      * @code
584      * br raoptions 0400ff00020001
585      * Done
586      * @endcode
587      * @code
588      * br raoptions clear
589      * Done
590      * @endcode
591      * @cparam br raoptions @ca{options|clear}
592      * `br raoptions clear` passes a `nullptr` to #otBorderRoutingSetExtraRouterAdvertOptions.
593      * Otherwise, you can pass the `options` byte as hex data.
594      * @par api_copy
595      * #otBorderRoutingSetExtraRouterAdvertOptions
596      */
597     if (aArgs[0] == "clear")
598     {
599         length = 0;
600     }
601     else
602     {
603         length = sizeof(options);
604         SuccessOrExit(error = aArgs[0].ParseAsHexString(length, options));
605     }
606 
607     error = otBorderRoutingSetExtraRouterAdvertOptions(GetInstancePtr(), length > 0 ? options : nullptr, length);
608 
609 exit:
610     return error;
611 }
612 
Process(Arg aArgs[])613 template <> otError Br::Process<Cmd("rioprf")>(Arg aArgs[])
614 {
615     otError error = OT_ERROR_NONE;
616 
617     /**
618      * @cli br rioprf
619      * @code
620      * br rioprf
621      * med
622      * Done
623      * @endcode
624      * @par api_copy
625      * #otBorderRoutingGetRouteInfoOptionPreference
626      */
627     if (aArgs[0].IsEmpty())
628     {
629         OutputLine("%s", PreferenceToString(otBorderRoutingGetRouteInfoOptionPreference(GetInstancePtr())));
630     }
631     /**
632      * @cli br rioprf clear
633      * @code
634      * br rioprf clear
635      * Done
636      * @endcode
637      * @par api_copy
638      * #otBorderRoutingClearRouteInfoOptionPreference
639      */
640     else if (aArgs[0] == "clear")
641     {
642         otBorderRoutingClearRouteInfoOptionPreference(GetInstancePtr());
643     }
644     /**
645      * @cli br rioprf (high,med,low)
646      * @code
647      * br rioprf low
648      * Done
649      * @endcode
650      * @cparam br rioprf [@ca{high}|@ca{med}|@ca{low}]
651      * @par api_copy
652      * #otBorderRoutingSetRouteInfoOptionPreference
653      */
654     else
655     {
656         otRoutePreference preference;
657 
658         SuccessOrExit(error = Interpreter::ParsePreference(aArgs[0], preference));
659         otBorderRoutingSetRouteInfoOptionPreference(GetInstancePtr(), preference);
660     }
661 
662 exit:
663     return error;
664 }
665 
Process(Arg aArgs[])666 template <> otError Br::Process<Cmd("routeprf")>(Arg aArgs[])
667 {
668     otError error = OT_ERROR_NONE;
669 
670     /**
671      * @cli br routeprf
672      * @code
673      * br routeprf
674      * med
675      * Done
676      * @endcode
677      * @par api_copy
678      * #otBorderRoutingGetRoutePreference
679      */
680     if (aArgs[0].IsEmpty())
681     {
682         OutputLine("%s", PreferenceToString(otBorderRoutingGetRoutePreference(GetInstancePtr())));
683     }
684     /**
685      * @cli br routeprf clear
686      * @code
687      * br routeprf clear
688      * Done
689      * @endcode
690      * @par api_copy
691      * #otBorderRoutingClearRoutePreference
692      */
693     else if (aArgs[0] == "clear")
694     {
695         otBorderRoutingClearRoutePreference(GetInstancePtr());
696     }
697     /**
698      * @cli br routeprf (high,med,low)
699      * @code
700      * br routeprf low
701      * Done
702      * @endcode
703      * @cparam br routeprf [@ca{high}|@ca{med}|@ca{low}]
704      * @par api_copy
705      * #otBorderRoutingSetRoutePreference
706      */
707     else
708     {
709         otRoutePreference preference;
710 
711         SuccessOrExit(error = Interpreter::ParsePreference(aArgs[0], preference));
712         otBorderRoutingSetRoutePreference(GetInstancePtr(), preference);
713     }
714 
715 exit:
716     return error;
717 }
718 
719 #if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE
720 
721 /**
722  * @cli br counters
723  * @code
724  * br counters
725  * Inbound Unicast: Packets 4 Bytes 320
726  * Inbound Multicast: Packets 0 Bytes 0
727  * Outbound Unicast: Packets 2 Bytes 160
728  * Outbound Multicast: Packets 0 Bytes 0
729  * RA Rx: 4
730  * RA TxSuccess: 2
731  * RA TxFailed: 0
732  * RS Rx: 0
733  * RS TxSuccess: 2
734  * RS TxFailed: 0
735  * Done
736  * @endcode
737  * @par api_copy
738  * #otIp6GetBorderRoutingCounters
739  */
Process(Arg aArgs[])740 template <> otError Br::Process<Cmd("counters")>(Arg aArgs[])
741 {
742     otError error = OT_ERROR_NONE;
743 
744     VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
745     Interpreter::GetInterpreter().OutputBorderRouterCounters();
746 
747 exit:
748     return error;
749 }
750 
751 #endif // OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE
752 
Process(Arg aArgs[])753 otError Br::Process(Arg aArgs[])
754 {
755 #define CmdEntry(aCommandString)                          \
756     {                                                     \
757         aCommandString, &Br::Process<Cmd(aCommandString)> \
758     }
759 
760     static constexpr Command kCommands[] = {
761 #if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE
762         CmdEntry("counters"),
763 #endif
764         CmdEntry("disable"),
765         CmdEntry("enable"),
766         CmdEntry("init"),
767 #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE
768         CmdEntry("nat64prefix"),
769 #endif
770         CmdEntry("omrprefix"),
771         CmdEntry("onlinkprefix"),
772 #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
773         CmdEntry("pd"),
774 #endif
775         CmdEntry("prefixtable"),
776         CmdEntry("raoptions"),
777         CmdEntry("rioprf"),
778         CmdEntry("routeprf"),
779         CmdEntry("routers"),
780         CmdEntry("state"),
781     };
782 
783 #undef CmdEntry
784 
785     static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
786 
787     otError        error = OT_ERROR_INVALID_COMMAND;
788     const Command *command;
789 
790     if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
791     {
792         OutputCommandTable(kCommands);
793         ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
794     }
795 
796     command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
797     VerifyOrExit(command != nullptr);
798 
799     error = (this->*command->mHandler)(aArgs + 1);
800 
801 exit:
802     return error;
803 }
804 
805 } // namespace Cli
806 } // namespace ot
807 
808 #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
809