1 /*
2  *  Copyright (c) 2019, 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 Joiner role.
32  */
33 
34 #include "cli_joiner.hpp"
35 
36 #include <inttypes.h>
37 
38 #include "cli/cli.hpp"
39 
40 #if OPENTHREAD_CONFIG_JOINER_ENABLE
41 
42 namespace ot {
43 namespace Cli {
44 
Process(Arg aArgs[])45 template <> otError Joiner::Process<Cmd("discerner")>(Arg aArgs[])
46 {
47     otError error = OT_ERROR_INVALID_ARGS;
48 
49     /**
50      * @cli joiner discerner
51      * @code
52      * joiner discerner
53      * 0xabc/12
54      * Done
55      * @endcode
56      * @par api_copy
57      * #otJoinerGetDiscerner
58      */
59     if (aArgs[0].IsEmpty())
60     {
61         const otJoinerDiscerner *discerner = otJoinerGetDiscerner(GetInstancePtr());
62 
63         VerifyOrExit(discerner != nullptr, error = OT_ERROR_NOT_FOUND);
64 
65         if (discerner->mValue <= 0xffffffff)
66         {
67             OutputLine("0x%lx/%u", static_cast<unsigned long>(discerner->mValue & 0xffffffff), discerner->mLength);
68         }
69         else
70         {
71             OutputLine("0x%lx%08lx/%u", static_cast<unsigned long>(discerner->mValue >> 32),
72                        static_cast<unsigned long>(discerner->mValue & 0xffffffff), discerner->mLength);
73         }
74 
75         error = OT_ERROR_NONE;
76     }
77     else
78     {
79         otJoinerDiscerner discerner;
80 
81         ClearAllBytes(discerner);
82 
83         /**
84          * @cli joiner discerner clear
85          * @code
86          * joiner discerner clear
87          * Done
88          * @endcode
89          * @par
90          * Clear the %Joiner discerner.
91          */
92         if (aArgs[0] == "clear")
93         {
94             error = otJoinerSetDiscerner(GetInstancePtr(), nullptr);
95         }
96         /**
97          * @cli joiner discerner (set)
98          * @code
99          * joiner discerner 0xabc/12
100          * Done
101          * @endcode
102          * @cparam joiner discerner @ca{discerner}
103          * *   Use `{number}/{length}` to set the `discerner`.
104          * *   `joiner discerner clear` sets `aDiscerner` to `nullptr`.
105          * @par api_copy
106          * #otJoinerSetDiscerner
107          */
108         else
109         {
110             VerifyOrExit(aArgs[1].IsEmpty());
111             SuccessOrExit(ParseJoinerDiscerner(aArgs[0], discerner));
112             error = otJoinerSetDiscerner(GetInstancePtr(), &discerner);
113         }
114     }
115 
116 exit:
117     return error;
118 }
119 
120 /**
121  * @cli joiner id
122  * @code
123  * joiner id
124  * d65e64fa83f81cf7
125  * Done
126  * @endcode
127  * @par api_copy
128  * #otJoinerGetId
129  */
Process(Arg aArgs[])130 template <> otError Joiner::Process<Cmd("id")>(Arg aArgs[])
131 {
132     OT_UNUSED_VARIABLE(aArgs);
133 
134     OutputExtAddressLine(*otJoinerGetId(GetInstancePtr()));
135 
136     return OT_ERROR_NONE;
137 }
138 
139 /**
140  * @cli joiner start
141  * @code
142  * joiner start J01NM3
143  * Done
144  * @endcode
145  * @cparam joiner start @ca{joining-device-credential} [@ca{provisioning-url}]
146  * *   `joining-device-credential`: %Joiner Passphrase. Must be a string of all uppercase alphanumeric
147  *     characters (0-9 and A-Y, excluding I, O, Q, and Z for readability), with a length between 6 and
148  *     32 characters.
149  * *   `provisioning-url`: Provisioning URL for the %Joiner (optional).
150  * @par api_copy
151  * #otJoinerStart
152  */
Process(Arg aArgs[])153 template <> otError Joiner::Process<Cmd("start")>(Arg aArgs[])
154 {
155     otError error;
156 
157     VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
158 
159     error = otJoinerStart(GetInstancePtr(),
160                           aArgs[0].GetCString(),           // aPskd
161                           aArgs[1].GetCString(),           // aProvisioningUrl (`nullptr` if aArgs[1] is empty)
162                           PACKAGE_NAME,                    // aVendorName
163                           OPENTHREAD_CONFIG_PLATFORM_INFO, // aVendorModel
164                           PACKAGE_VERSION,                 // aVendorSwVersion
165                           nullptr,                         // aVendorData
166                           &Joiner::HandleCallback, this);
167 
168 exit:
169     return error;
170 }
171 
172 /**
173  * @cli joiner stop
174  * @code
175  * joiner stop
176  * Done
177  * @endcode
178  * @par api_copy
179  * #otJoinerStop
180  */
Process(Arg aArgs[])181 template <> otError Joiner::Process<Cmd("stop")>(Arg aArgs[])
182 {
183     OT_UNUSED_VARIABLE(aArgs);
184 
185     otJoinerStop(GetInstancePtr());
186 
187     return OT_ERROR_NONE;
188 }
189 
190 /**
191  * @cli joiner state
192  * @code
193  * joiner state
194  * Idle
195  * Done
196  * @endcode
197  * @par api_copy
198  * #otJoinerGetState
199  * @par
200  * Returns one of the following states:
201  * *   `Idle`
202  * *   `Discover`
203  * *   `Connecting`
204  * *   `Connected`
205  * *   `Entrust`
206  * *   `Joined`
207  */
Process(Arg aArgs[])208 template <> otError Joiner::Process<Cmd("state")>(Arg aArgs[])
209 {
210     OT_UNUSED_VARIABLE(aArgs);
211 
212     OutputLine("%s", otJoinerStateToString(otJoinerGetState(GetInstancePtr())));
213 
214     return OT_ERROR_NONE;
215 }
216 
Process(Arg aArgs[])217 otError Joiner::Process(Arg aArgs[])
218 {
219 #define CmdEntry(aCommandString)                              \
220     {                                                         \
221         aCommandString, &Joiner::Process<Cmd(aCommandString)> \
222     }
223 
224     static constexpr Command kCommands[] = {
225         CmdEntry("discerner"), CmdEntry("id"), CmdEntry("start"), CmdEntry("state"), CmdEntry("stop"),
226     };
227 
228 #undef CmdEntry
229 
230     static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
231 
232     otError        error = OT_ERROR_INVALID_COMMAND;
233     const Command *command;
234 
235     /**
236      * @cli joiner help
237      * @code
238      * joiner help
239      * help
240      * id
241      * start
242      * state
243      * stop
244      * Done
245      * @endcode
246      * @par
247      * Print the `joiner` help menu.
248      */
249     if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
250     {
251         OutputCommandTable(kCommands);
252         ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
253     }
254 
255     command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
256     VerifyOrExit(command != nullptr);
257 
258     error = (this->*command->mHandler)(aArgs + 1);
259 
260 exit:
261     return error;
262 }
263 
HandleCallback(otError aError,void * aContext)264 void Joiner::HandleCallback(otError aError, void *aContext) { static_cast<Joiner *>(aContext)->HandleCallback(aError); }
265 
HandleCallback(otError aError)266 void Joiner::HandleCallback(otError aError)
267 {
268     switch (aError)
269     {
270     case OT_ERROR_NONE:
271         OutputLine("Join success");
272         break;
273 
274     default:
275         OutputLine("Join failed [%s]", otThreadErrorToString(aError));
276         break;
277     }
278 }
279 
280 } // namespace Cli
281 } // namespace ot
282 
283 #endif // OPENTHREAD_CONFIG_JOINER_ENABLE
284