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