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