1 /*
2  * Copyright 2017, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.commands.lowpan;
18 
19 import android.net.LinkAddress;
20 import android.net.lowpan.ILowpanInterface;
21 import android.net.lowpan.LowpanBeaconInfo;
22 import android.net.lowpan.LowpanCredential;
23 import android.net.lowpan.LowpanEnergyScanResult;
24 import android.net.lowpan.LowpanException;
25 import android.net.lowpan.LowpanIdentity;
26 import android.net.lowpan.LowpanInterface;
27 import android.net.lowpan.LowpanManager;
28 import android.net.lowpan.LowpanProvision;
29 import android.net.lowpan.LowpanScanner;
30 import android.os.RemoteException;
31 import android.os.ServiceSpecificException;
32 import android.util.AndroidRuntimeException;
33 import com.android.internal.os.BaseCommand;
34 import com.android.internal.util.HexDump;
35 import java.io.PrintStream;
36 import java.util.HashMap;
37 import java.util.Map;
38 import java.util.concurrent.Semaphore;
39 import java.util.concurrent.TimeUnit;
40 
41 public class LowpanCtl extends BaseCommand {
42     private LowpanManager mLowpanManager;
43     private LowpanInterface mLowpanInterface;
44     private ILowpanInterface mILowpanInterface;
45     private String mLowpanInterfaceName;
46 
47     /**
48      * Command-line entry point.
49      *
50      * @param args The command-line arguments
51      */
main(String[] args)52     public static void main(String[] args) {
53         new LowpanCtl().run(args);
54     }
55 
56     @Override
onShowUsage(PrintStream out)57     public void onShowUsage(PrintStream out) {
58         out.println(
59                 "usage: lowpanctl [options] [subcommand] [subcommand-options]\n"
60                         + "options:\n"
61                         + "       -I / --interface <iface-name> ..... Interface Name\n"
62                         + "subcommands:\n"
63                         + "       lowpanctl status\n"
64                         + "       lowpanctl form\n"
65                         + "       lowpanctl join\n"
66                         + "       lowpanctl attach\n"
67                         + "       lowpanctl leave\n"
68                         + "       lowpanctl enable\n"
69                         + "       lowpanctl disable\n"
70                         + "       lowpanctl show-credential\n"
71                         + "       lowpanctl scan\n"
72                         + "       lowpanctl reset\n"
73                         + "       lowpanctl list\n"
74                         + "\n"
75                         + "usage: lowpanctl [options] join/form/attach [network-name]\n"
76                         + "subcommand-options:\n"
77                         + "       --name <network-name> ............. Network Name\n"
78                         + "       -p / --panid <panid> .............. PANID\n"
79                         + "       -c / --channel <channel> .......... Channel Index\n"
80                         + "       -x / --xpanid <xpanid> ............ XPANID\n"
81                         + "       -k / --master-key <master-key> .... Master Key\n"
82                         + "       --master-key-index <key-index> .... Key Index\n"
83                         + "\n"
84                         + "usage: lowpanctl [options] show-credential\n"
85                         + "subcommand-options:\n"
86                         + "       -r / --raw ........................ Print only key contents\n"
87                         + "\n");
88     }
89 
90     private class CommandErrorException extends AndroidRuntimeException {
CommandErrorException(String desc)91         public CommandErrorException(String desc) {
92             super(desc);
93         }
94     }
95 
96     private class ArgumentErrorException extends IllegalArgumentException {
ArgumentErrorException(String desc)97         public ArgumentErrorException(String desc) {
98             super(desc);
99         }
100     }
101 
throwCommandError(String desc)102     private void throwCommandError(String desc) {
103         throw new CommandErrorException(desc);
104     }
105 
throwArgumentError(String desc)106     private void throwArgumentError(String desc) {
107         throw new ArgumentErrorException(desc);
108     }
109 
getLowpanManager()110     private LowpanManager getLowpanManager() {
111         if (mLowpanManager == null) {
112             mLowpanManager = LowpanManager.getManager();
113 
114             if (mLowpanManager == null) {
115                 System.err.println(NO_SYSTEM_ERROR_CODE);
116                 throwCommandError("Can't connect to LoWPAN service; is the service running?");
117             }
118         }
119         return mLowpanManager;
120     }
121 
getLowpanInterface()122     private LowpanInterface getLowpanInterface() {
123         if (mLowpanInterface == null) {
124             if (mLowpanInterfaceName == null) {
125                 String interfaceArray[] = getLowpanManager().getInterfaceList();
126                 if (interfaceArray.length != 0) {
127                     mLowpanInterfaceName = interfaceArray[0];
128                 } else {
129                     throwCommandError("No LoWPAN interfaces are present");
130                 }
131             }
132             mLowpanInterface = getLowpanManager().getInterface(mLowpanInterfaceName);
133 
134             if (mLowpanInterface == null) {
135                 throwCommandError("Unknown LoWPAN interface \"" + mLowpanInterfaceName + "\"");
136             }
137         }
138         return mLowpanInterface;
139     }
140 
getILowpanInterface()141     private ILowpanInterface getILowpanInterface() {
142         if (mILowpanInterface == null) {
143             mILowpanInterface = getLowpanInterface().getService();
144         }
145         return mILowpanInterface;
146     }
147 
148     @Override
onRun()149     public void onRun() throws Exception {
150         try {
151             String op;
152             while ((op = nextArgRequired()) != null) {
153                 if (op.equals("-I") || op.equals("--interface")) {
154                     mLowpanInterfaceName = nextArgRequired();
155                 } else if (op.equals("-h") || op.equals("--help") || op.equals("help")) {
156                     onShowUsage(System.out);
157                     break;
158                 } else if (op.startsWith("-")) {
159                     throwArgumentError("Unrecognized argument \"" + op + "\"");
160                 } else if (op.equals("status") || op.equals("stat")) {
161                     runStatus();
162                     break;
163                 } else if (op.equals("scan") || op.equals("netscan") || op.equals("ns")) {
164                     runNetScan();
165                     break;
166                 } else if (op.equals("attach")) {
167                     runAttach();
168                     break;
169                 } else if (op.equals("enable")) {
170                     runEnable();
171                     break;
172                 } else if (op.equals("disable")) {
173                     runDisable();
174                     break;
175                 } else if (op.equals("show-credential")) {
176                     runShowCredential();
177                     break;
178                 } else if (op.equals("join")) {
179                     runJoin();
180                     break;
181                 } else if (op.equals("form")) {
182                     runForm();
183                     break;
184                 } else if (op.equals("leave")) {
185                     runLeave();
186                     break;
187                 } else if (op.equals("energyscan") || op.equals("energy") || op.equals("es")) {
188                     runEnergyScan();
189                     break;
190                 } else if (op.equals("list") || op.equals("ls")) {
191                     runListInterfaces();
192                     break;
193                 } else if (op.equals("reset")) {
194                     runReset();
195                     break;
196                 } else {
197                     throwArgumentError("Unrecognized argument \"" + op + "\"");
198                     break;
199                 }
200             }
201         } catch (ServiceSpecificException x) {
202             System.out.println(
203                     "ServiceSpecificException: " + x.errorCode + ": " + x.getLocalizedMessage());
204             throw x;
205 
206         } catch (ArgumentErrorException x) {
207             // Rethrow so we get syntax help.
208             throw x;
209 
210         } catch (IllegalArgumentException x) {
211             // This was an argument exception that wasn't an
212             // argument error. We dump our stack trace immediately
213             // because this might not be from a command line argument.
214             x.printStackTrace(System.err);
215             System.exit(1);
216 
217         } catch (CommandErrorException x) {
218             // Command errors are normal errors that just
219             // get printed out without any fanfare.
220             System.out.println("error: " + x.getLocalizedMessage());
221             System.exit(1);
222         }
223     }
224 
runReset()225     private void runReset() throws LowpanException {
226         getLowpanInterface().reset();
227     }
228 
runEnable()229     private void runEnable() throws LowpanException {
230         getLowpanInterface().setEnabled(true);
231     }
232 
runDisable()233     private void runDisable() throws LowpanException {
234         getLowpanInterface().setEnabled(false);
235     }
236 
getProvisionFromArgs(boolean credentialRequired)237     private LowpanProvision getProvisionFromArgs(boolean credentialRequired) {
238         LowpanProvision.Builder builder = new LowpanProvision.Builder();
239         Map<String, Object> properties = new HashMap();
240         LowpanIdentity.Builder identityBuilder = new LowpanIdentity.Builder();
241         LowpanCredential credential = null;
242         String arg;
243         byte[] masterKey = null;
244         int masterKeyIndex = 0;
245         boolean hasName = false;
246 
247         while ((arg = nextArg()) != null) {
248             if (arg.equals("--name")) {
249                 identityBuilder.setName(nextArgRequired());
250                 hasName = true;
251             } else if (arg.equals("-p") || arg.equals("--panid")) {
252                 identityBuilder.setPanid(Integer.decode(nextArgRequired()));
253             } else if (arg.equals("-c") || arg.equals("--channel")) {
254                 identityBuilder.setChannel(Integer.decode(nextArgRequired()));
255             } else if (arg.equals("-x") || arg.equals("--xpanid")) {
256                 identityBuilder.setXpanid(HexDump.hexStringToByteArray(nextArgRequired()));
257             } else if (arg.equals("-k") || arg.equals("--master-key")) {
258                 masterKey = HexDump.hexStringToByteArray(nextArgRequired());
259             } else if (arg.equals("--master-key-index")) {
260                 masterKeyIndex = Integer.decode(nextArgRequired());
261             } else if (arg.startsWith("-") || hasName) {
262                 throwArgumentError("Unrecognized argument \"" + arg + "\"");
263             } else {
264                 // This is the network name
265                 identityBuilder.setName(arg);
266                 hasName = true;
267             }
268         }
269 
270         if (credential == null && masterKey != null) {
271             if (masterKeyIndex == 0) {
272                 credential = LowpanCredential.createMasterKey(masterKey);
273             } else {
274                 credential = LowpanCredential.createMasterKey(masterKey, masterKeyIndex);
275             }
276         }
277 
278         if (credential != null) {
279             builder.setLowpanCredential(credential);
280         } else if (credentialRequired) {
281             throwArgumentError("No credential (like a master key) was specified!");
282         }
283 
284         return builder.setLowpanIdentity(identityBuilder.build()).build();
285     }
286 
runAttach()287     private void runAttach() throws LowpanException {
288         LowpanProvision provision = getProvisionFromArgs(true);
289 
290         System.out.println(
291                 "Attaching to " + provision.getLowpanIdentity() + " with provided credential");
292 
293         getLowpanInterface().attach(provision);
294 
295         System.out.println("Attached.");
296     }
297 
runLeave()298     private void runLeave() throws LowpanException {
299         getLowpanInterface().leave();
300     }
301 
runJoin()302     private void runJoin() throws LowpanException {
303         LowpanProvision provision = getProvisionFromArgs(true);
304 
305         System.out.println(
306                 "Joining " + provision.getLowpanIdentity() + " with provided credential");
307 
308         getLowpanInterface().join(provision);
309 
310         System.out.println("Joined.");
311     }
312 
runForm()313     private void runForm() throws LowpanException {
314         LowpanProvision provision = getProvisionFromArgs(false);
315 
316         if (provision.getLowpanCredential() != null) {
317             System.out.println(
318                     "Forming " + provision.getLowpanIdentity() + " with provided credential");
319         } else {
320             System.out.println("Forming " + provision.getLowpanIdentity());
321         }
322 
323         getLowpanInterface().form(provision);
324 
325         System.out.println("Formed.");
326     }
327 
runStatus()328     private void runStatus() throws LowpanException, RemoteException {
329         LowpanInterface iface = getLowpanInterface();
330         StringBuffer sb = new StringBuffer();
331 
332         sb.append(iface.getName())
333                 .append("\t")
334                 .append(iface.getState());
335 
336         if (!iface.isEnabled()) {
337             sb.append(" DISABLED");
338 
339         } else if (iface.getState() != LowpanInterface.STATE_FAULT) {
340             sb.append(" (" + iface.getRole() + ")");
341 
342             if (iface.isUp()) {
343                 sb.append(" UP");
344             }
345 
346             if (iface.isConnected()) {
347                 sb.append(" CONNECTED");
348             }
349 
350             if (iface.isCommissioned()) {
351                 sb.append(" COMMISSIONED");
352 
353                 LowpanIdentity identity = getLowpanInterface().getLowpanIdentity();
354 
355                 if (identity != null) {
356                     sb.append("\n\t").append(identity);
357                 }
358             }
359 
360             if (iface.isUp()) {
361                 for (LinkAddress addr : iface.getLinkAddresses()) {
362                     sb.append("\n\t").append(addr);
363                 }
364             }
365         }
366 
367         sb.append("\n");
368         System.out.println(sb.toString());
369     }
370 
runShowCredential()371     private void runShowCredential() throws LowpanException, RemoteException {
372         LowpanInterface iface = getLowpanInterface();
373         boolean raw = false;
374         String arg;
375         while ((arg = nextArg()) != null) {
376             if (arg.equals("--raw") || arg.equals("-r")) {
377                 raw = true;
378             } else {
379                 throwArgumentError("Unrecognized argument \"" + arg + "\"");
380             }
381         }
382 
383         LowpanCredential credential = iface.getLowpanCredential();
384         if (raw) {
385             System.out.println(HexDump.toHexString(credential.getMasterKey()));
386         } else {
387             System.out.println(iface.getName() + "\t" + credential.toSensitiveString());
388         }
389     }
390 
runListInterfaces()391     private void runListInterfaces() {
392         for (String name : getLowpanManager().getInterfaceList()) {
393             System.out.println(name);
394         }
395     }
396 
runNetScan()397     private void runNetScan() throws LowpanException, InterruptedException {
398         LowpanScanner scanner = getLowpanInterface().createScanner();
399         String arg;
400 
401         while ((arg = nextArg()) != null) {
402             if (arg.equals("-c") || arg.equals("--channel")) {
403                 scanner.addChannel(Integer.decode(nextArgRequired()));
404             } else {
405                 throwArgumentError("Unrecognized argument \"" + arg + "\"");
406             }
407         }
408 
409         Semaphore semaphore = new Semaphore(1);
410 
411         scanner.setCallback(
412                 new LowpanScanner.Callback() {
413                     @Override
414                     public void onNetScanBeacon(LowpanBeaconInfo beacon) {
415                         System.out.println(beacon.toString());
416                     }
417 
418                     @Override
419                     public void onScanFinished() {
420                         semaphore.release();
421                     }
422                 });
423 
424         semaphore.acquire();
425         scanner.startNetScan();
426 
427         // Wait for our scan to complete.
428         if (semaphore.tryAcquire(1, 60L, TimeUnit.SECONDS)) {
429             semaphore.release();
430         } else {
431             throwCommandError("Timeout while waiting for scan to complete.");
432         }
433     }
434 
runEnergyScan()435     private void runEnergyScan() throws LowpanException, InterruptedException {
436         LowpanScanner scanner = getLowpanInterface().createScanner();
437         String arg;
438 
439         while ((arg = nextArg()) != null) {
440             if (arg.equals("-c") || arg.equals("--channel")) {
441                 scanner.addChannel(Integer.decode(nextArgRequired()));
442             } else {
443                 throwArgumentError("Unrecognized argument \"" + arg + "\"");
444             }
445         }
446 
447         Semaphore semaphore = new Semaphore(1);
448 
449         scanner.setCallback(
450                 new LowpanScanner.Callback() {
451                     @Override
452                     public void onEnergyScanResult(LowpanEnergyScanResult result) {
453                         System.out.println(result.toString());
454                     }
455 
456                     @Override
457                     public void onScanFinished() {
458                         semaphore.release();
459                     }
460                 });
461 
462         semaphore.acquire();
463         scanner.startEnergyScan();
464 
465         // Wait for our scan to complete.
466         if (semaphore.tryAcquire(1, 60L, TimeUnit.SECONDS)) {
467             semaphore.release();
468         } else {
469             throwCommandError("Timeout while waiting for scan to complete.");
470         }
471     }
472 }
473