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