summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Quattlebaum <rquattle@google.com>2017-05-15 16:01:57 -0700
committerRobert Quattlebaum <rquattle@google.com>2017-07-06 11:40:33 -0700
commitf15236e9c6adc5f69be5152bc61fe19bc975c222 (patch)
tree583f3e1c76e2999aaee5315748f588c72278dd5c
parent3c0a5570fdcc6a01e1a3a90337eebf3e4e6d19b8 (diff)
downloadlowpan-f15236e9c6adc5f69be5152bc61fe19bc975c222.tar.gz
Initial commit of LoWPAN Command (lowpanctl)
This is a command-line tool for managing LoWPAN networks. Bug: b/33073713 Change-Id: Id4a371bfe00c7de0ebb47f186eb1a286d81bad5a
-rw-r--r--command/Android.mk36
-rw-r--r--command/java/com/android/commands/lowpan/LowpanCtl.java445
-rwxr-xr-xcommand/lowpanctl6
3 files changed, 487 insertions, 0 deletions
diff --git a/command/Android.mk b/command/Android.mk
new file mode 100644
index 0000000..82c0ffd
--- /dev/null
+++ b/command/Android.mk
@@ -0,0 +1,36 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+ifneq (,$(findstring lowpan/java,$(FRAMEWORKS_BASE_SUBDIRS)))
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := lowpan-command
+LOCAL_SRC_FILES += java/com/android/commands/lowpan/LowpanCtl.java
+include $(BUILD_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := lowpanctl
+LOCAL_MODULE_TAGS := optional
+LOCAL_REQUIRED_MODULES := lowpan-command
+LOCAL_SRC_FILES := lowpanctl
+LOCAL_MODULE_CLASS := EXECUTABLES
+include $(BUILD_PREBUILT)
+
+endif
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/command/java/com/android/commands/lowpan/LowpanCtl.java b/command/java/com/android/commands/lowpan/LowpanCtl.java
new file mode 100644
index 0000000..e23c094
--- /dev/null
+++ b/command/java/com/android/commands/lowpan/LowpanCtl.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.lowpan;
+
+import android.net.lowpan.ILowpanInterface;
+import android.net.lowpan.LowpanBeaconInfo;
+import android.net.lowpan.LowpanCredential;
+import android.net.lowpan.LowpanEnergyScanResult;
+import android.net.lowpan.LowpanException;
+import android.net.lowpan.LowpanIdentity;
+import android.net.lowpan.LowpanInterface;
+import android.net.lowpan.LowpanManager;
+import android.net.lowpan.LowpanProvision;
+import android.net.lowpan.LowpanScanner;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.AndroidRuntimeException;
+import com.android.internal.os.BaseCommand;
+import com.android.internal.util.HexDump;
+import java.io.PrintStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+public class LowpanCtl extends BaseCommand {
+ private LowpanManager mLowpanManager;
+ private LowpanInterface mLowpanInterface;
+ private ILowpanInterface mILowpanInterface;
+ private String mLowpanInterfaceName;
+
+ /**
+ * Command-line entry point.
+ *
+ * @param args The command-line arguments
+ */
+ public static void main(String[] args) {
+ new LowpanCtl().run(args);
+ }
+
+ @Override
+ public void onShowUsage(PrintStream out) {
+ out.println(
+ "usage: lowpanctl [options] [subcommand] [subcommand-options]\n"
+ + " lowpanctl status\n"
+ + " lowpanctl form\n"
+ + " lowpanctl join\n"
+ + " lowpanctl leave\n"
+ + " lowpanctl up\n"
+ + " lowpanctl down\n"
+ + " lowpanctl get [property-name]\n"
+ + " lowpanctl set [property-name]\n"
+ + " lowpanctl scan\n"
+ + " lowpanctl reset\n"
+ + " lowpanctl list\n"
+ + "\n");
+ }
+
+ private class CommandErrorException extends AndroidRuntimeException {
+ public CommandErrorException(String desc) {
+ super(desc);
+ }
+ }
+
+ private void throwCommandError(String desc) {
+ throw new CommandErrorException(desc);
+ }
+
+ private LowpanInterface getLowpanInterface() {
+ if (mLowpanInterface == null) {
+ if (mLowpanInterfaceName == null) {
+ String interfaceArray[] = mLowpanManager.getInterfaceList();
+ if (interfaceArray.length != 0) {
+ mLowpanInterfaceName = interfaceArray[0];
+ } else {
+ throwCommandError("No LoWPAN interfaces are present");
+ }
+ }
+ mLowpanInterface = mLowpanManager.getInterface(mLowpanInterfaceName);
+
+ if (mLowpanInterface == null) {
+ throwCommandError("Unknown LoWPAN interface \"" + mLowpanInterfaceName + "\"");
+ }
+ }
+ return mLowpanInterface;
+ }
+
+ private ILowpanInterface getILowpanInterface() {
+ if (mILowpanInterface == null) {
+ mILowpanInterface = getLowpanInterface().getService();
+ }
+ return mILowpanInterface;
+ }
+
+ @Override
+ public void onRun() throws Exception {
+ mLowpanManager = LowpanManager.getManager();
+
+ if (mLowpanManager == null) {
+ System.err.println(NO_SYSTEM_ERROR_CODE);
+ throwCommandError("Can't connect to LoWPAN service; is the service running?");
+ }
+
+ try {
+ String op;
+ while ((op = nextArgRequired()) != null) {
+ if (op.equals("-I") || op.equals("--interface")) {
+ mLowpanInterfaceName = nextArgRequired();
+ } else if (op.startsWith("-")) {
+ throwCommandError("Unrecognized argument \"" + op + "\"");
+ } else if (op.equals("status") || op.equals("stat")) {
+ runStatus();
+ break;
+ } else if (op.equals("scan") || op.equals("netscan") || op.equals("ns")) {
+ runNetScan();
+ break;
+ } else if (op.equals("attach") || op.equals("up")) {
+ runAttach();
+ break;
+ } else if (op.equals("detach") || op.equals("down")) {
+ runDetach();
+ break;
+ } else if (op.equals("join")) {
+ runJoin();
+ break;
+ } else if (op.equals("form")) {
+ runForm();
+ break;
+ } else if (op.equals("leave")) {
+ runLeave();
+ break;
+ } else if (op.equals("get") || op.equals("getprop")) {
+ runGetProp();
+ break;
+ } else if (op.equals("set") || op.equals("setprop")) {
+ runSetProp();
+ break;
+ } else if (op.equals("energyscan") || op.equals("energy") || op.equals("es")) {
+ runEnergyScan();
+ break;
+ } else if (op.equals("list") || op.equals("ls")) {
+ runListInterfaces();
+ break;
+ } else if (op.equals("reset")) {
+ runReset();
+ break;
+ } else {
+ showError("Error: unknown command '" + op + "'");
+ break;
+ }
+ }
+ } catch (ServiceSpecificException x) {
+ System.out.println(
+ "ServiceSpecificException: " + x.errorCode + ": " + x.getLocalizedMessage());
+ } catch (CommandErrorException x) {
+ System.out.println("error: " + x.getLocalizedMessage());
+ }
+ }
+
+ private void runReset() throws LowpanException {
+ getLowpanInterface().reset();
+ }
+
+ private LowpanProvision getProvisionFromArgs(boolean credentialRequired) {
+ LowpanProvision.Builder builder = new LowpanProvision.Builder();
+ Map<String, Object> properties = new HashMap();
+ LowpanIdentity.Builder identityBuilder = new LowpanIdentity.Builder();
+ LowpanCredential credential = null;
+ String arg;
+ byte[] masterKey = null;
+ int masterKeyIndex = 0;
+ boolean hasName = false;
+
+ while ((arg = nextArg()) != null) {
+ if (arg.equals("--name")) {
+ identityBuilder.setName(nextArgRequired());
+ hasName = true;
+ } else if (arg.equals("-p") || arg.equals("--panid")) {
+ identityBuilder.setPanid(Integer.decode(nextArgRequired()));
+ } else if (arg.equals("-c") || arg.equals("--channel")) {
+ identityBuilder.setChannel(Integer.decode(nextArgRequired()));
+ } else if (arg.equals("-x") || arg.equals("--xpanid")) {
+ identityBuilder.setXpanid(HexDump.hexStringToByteArray(nextArgRequired()));
+ } else if (arg.equals("-k") || arg.equals("--master-key")) {
+ masterKey = HexDump.hexStringToByteArray(nextArgRequired());
+ } else if (arg.equals("--master-key-index")) {
+ masterKeyIndex = Integer.decode(nextArgRequired());
+ } else if (arg.startsWith("-") || hasName) {
+ throwCommandError("Unrecognized argument \"" + arg + "\"");
+ } else {
+ // This is the network name
+ identityBuilder.setName(arg);
+ hasName = true;
+ }
+ }
+
+ if (credential == null && masterKey != null) {
+ if (masterKeyIndex == 0) {
+ credential = LowpanCredential.createMasterKey(masterKey);
+ } else {
+ credential = LowpanCredential.createMasterKey(masterKey, masterKeyIndex);
+ }
+ }
+
+ if (credential != null) {
+ builder.setLowpanCredential(credential);
+ } else if (credentialRequired) {
+ throwCommandError("No credential (like a master key) was specified!");
+ }
+
+ return builder.setLowpanIdentity(identityBuilder.build()).build();
+ }
+
+ private void runAttach() throws LowpanException {
+ LowpanProvision provision = getProvisionFromArgs(true);
+
+ System.out.println(
+ "Attaching to " + provision.getLowpanIdentity() + " with provided credential");
+
+ getLowpanInterface().attach(provision);
+
+ System.out.println("Attached.");
+ }
+
+ private void runDetach() throws LowpanException {
+ getLowpanInterface().setUp(false);
+ }
+
+ private void runLeave() throws LowpanException {
+ getLowpanInterface().leave();
+ }
+
+ private void runJoin() throws LowpanException {
+ LowpanProvision provision = getProvisionFromArgs(true);
+
+ System.out.println(
+ "Joining " + provision.getLowpanIdentity() + " with provided credential");
+
+ getLowpanInterface().join(provision);
+
+ System.out.println("Joined.");
+ }
+
+ private void runForm() throws LowpanException {
+ LowpanProvision provision = getProvisionFromArgs(false);
+
+ if (provision.getLowpanCredential() != null) {
+ System.out.println(
+ "Forming "
+ + provision.getLowpanIdentity().toString()
+ + " with provided credential");
+ } else {
+ System.out.println("Forming " + provision.getLowpanIdentity().toString());
+ }
+
+ getLowpanInterface().form(provision);
+
+ System.out.println("Formed.");
+ }
+
+ private String propAsString(String key, Object value) {
+ if (value instanceof byte[]) {
+ value = HexDump.toHexString((byte[]) value);
+ } else if (value instanceof String[]) {
+ if (((String[]) value).length == 0) {
+ value = "{ }";
+ } else {
+ String renderedValue = "{\n";
+ for (String row : (String[]) value) {
+ renderedValue += "\t\"" + row + "\"\n";
+ }
+ value = renderedValue + "}";
+ }
+ } else if (value instanceof int[]) {
+ if (((int[]) value).length == 0) {
+ value = "{ }";
+ } else {
+ String renderedValue = "{\n";
+ for (int row : (int[]) value) {
+ renderedValue += "\t" + Integer.toString(row) + "\n";
+ }
+ value = renderedValue + "}";
+ }
+ } else if ((value instanceof Long) && (key.equals(ILowpanInterface.KEY_NETWORK_XPANID))) {
+ value = "0x" + Long.toHexString((Long) value);
+ } else if ((value instanceof Integer)
+ && (key.equals(ILowpanInterface.KEY_NETWORK_PANID)
+ || key.equals("Thread:RLOC16"))) {
+ value = String.format("0x%04X", (Integer) value & 0xFFFF);
+ }
+ return value.toString();
+ }
+
+ private void runGetProp() throws LowpanException, RemoteException {
+ String key = nextArg();
+
+ if (key == null) {
+ try {
+ String key_list[] = getILowpanInterface().getPropertyKeys();
+
+ for (String subkey : key_list) {
+ Object value;
+ try {
+ value = getILowpanInterface().getPropertyAsString(subkey);
+ } catch (Exception x) {
+ value = x;
+ }
+ System.out.println(subkey + " => " + propAsString(subkey, value));
+ }
+ } catch (RemoteException x) {
+ x.rethrowAsRuntimeException();
+ }
+ } else {
+ Object value = getILowpanInterface().getPropertyAsString(key);
+ System.out.println(propAsString(key, value));
+ }
+ }
+
+ private void runSetProp() {
+ System.out.println("Command not implemented");
+ }
+
+ private void runStatus() throws LowpanException, RemoteException {
+ String statusKeys[] = {
+ ILowpanInterface.KEY_INTERFACE_ENABLED,
+ ILowpanInterface.KEY_INTERFACE_STATE,
+ "org.wpantund.Daemon:Version",
+ "org.wpantund.NCP:Version",
+ "org.wpantund.Config:NCP:DriverName",
+ "org.wpantund.IPv6:LinkLocalAddress",
+ "org.wpantund.IPv6:MeshLocalAddress",
+ };
+ System.out.println(
+ "Current Network => " + getLowpanInterface().getLowpanIdentity().toString());
+
+ for (String key : statusKeys) {
+ Object value;
+ try {
+ value = getILowpanInterface().getPropertyAsString(key);
+ if (value != null) {
+ System.out.println(key + " => " + propAsString(key, value));
+ }
+ } catch (ServiceSpecificException x) {
+ // Skip keys which cause remote exceptions.
+ }
+ }
+ }
+
+ private void runListInterfaces() {
+ for (String name : mLowpanManager.getInterfaceList()) {
+ System.out.println(name);
+ }
+ }
+
+ private void runNetScan() throws LowpanException, InterruptedException {
+ LowpanScanner scanner = getLowpanInterface().createScanner();
+ String arg;
+
+ while ((arg = nextArg()) != null) {
+ if (arg.equals("-c") || arg.equals("--channel")) {
+ scanner.addChannel(Integer.decode(nextArgRequired()));
+ } else {
+ throwCommandError("Unrecognized argument \"" + arg + "\"");
+ }
+ }
+
+ Semaphore semaphore = new Semaphore(1);
+
+ scanner.setCallback(
+ new LowpanScanner.Callback() {
+ @Override
+ public void onNetScanBeacon(LowpanBeaconInfo beacon) {
+ System.out.println(beacon.toString());
+ }
+
+ @Override
+ public void onScanFinished() {
+ semaphore.release();
+ }
+ });
+
+ semaphore.acquire();
+ scanner.startNetScan();
+
+ // Wait for our scan to complete.
+ if (semaphore.tryAcquire(1, 60L, TimeUnit.SECONDS)) {
+ semaphore.release();
+ } else {
+ throwCommandError("Timeout while waiting for scan to complete.");
+ }
+ }
+
+ private void runEnergyScan() throws LowpanException, InterruptedException {
+ LowpanScanner scanner = getLowpanInterface().createScanner();
+ String arg;
+
+ while ((arg = nextArg()) != null) {
+ if (arg.equals("-c") || arg.equals("--channel")) {
+ scanner.addChannel(Integer.decode(nextArgRequired()));
+ } else {
+ throwCommandError("Unrecognized argument \"" + arg + "\"");
+ }
+ }
+
+ Semaphore semaphore = new Semaphore(1);
+
+ scanner.setCallback(
+ new LowpanScanner.Callback() {
+ @Override
+ public void onEnergyScanResult(LowpanEnergyScanResult result) {
+ System.out.println(result.toString());
+ }
+
+ @Override
+ public void onScanFinished() {
+ semaphore.release();
+ }
+ });
+
+ semaphore.acquire();
+ scanner.startEnergyScan();
+
+ // Wait for our scan to complete.
+ if (semaphore.tryAcquire(1, 60L, TimeUnit.SECONDS)) {
+ semaphore.release();
+ } else {
+ throwCommandError("Timeout while waiting for scan to complete.");
+ }
+ }
+}
diff --git a/command/lowpanctl b/command/lowpanctl
new file mode 100755
index 0000000..188b9d6
--- /dev/null
+++ b/command/lowpanctl
@@ -0,0 +1,6 @@
+# Script to start "lowpan-command" on the device, which has a very rudimentary
+# shell.
+#
+base=/system
+export CLASSPATH=$base/framework/lowpan-command.jar
+exec app_process $base/bin com.android.commands.lowpan.LowpanCtl "$@"