[Thread] add CLI commands for getting/setting config
Bug: 364237115 Change-Id: Ice42d7d02cfefd733042282939635502c259d9d5
This commit is contained in:
parent
fe6dedde08
commit
7eb1bbd369
4 changed files with 207 additions and 0 deletions
|
|
@ -19,10 +19,12 @@ package com.android.server.thread;
|
|||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.net.thread.ActiveOperationalDataset;
|
||||
import android.net.thread.IConfigurationReceiver;
|
||||
import android.net.thread.IOperationReceiver;
|
||||
import android.net.thread.IOutputReceiver;
|
||||
import android.net.thread.OperationalDatasetTimestamp;
|
||||
import android.net.thread.PendingOperationalDataset;
|
||||
import android.net.thread.ThreadConfiguration;
|
||||
import android.net.thread.ThreadNetworkException;
|
||||
import android.os.Binder;
|
||||
import android.os.Process;
|
||||
|
|
@ -56,6 +58,7 @@ public final class ThreadNetworkShellCommand extends BasicShellCommandHandler {
|
|||
private static final Duration MIGRATE_TIMEOUT = Duration.ofSeconds(2);
|
||||
private static final Duration FORCE_STOP_TIMEOUT = Duration.ofSeconds(1);
|
||||
private static final Duration OT_CTL_COMMAND_TIMEOUT = Duration.ofSeconds(5);
|
||||
private static final Duration CONFIG_TIMEOUT = Duration.ofSeconds(1);
|
||||
private static final String PERMISSION_THREAD_NETWORK_TESTING =
|
||||
"android.permission.THREAD_NETWORK_TESTING";
|
||||
|
||||
|
|
@ -118,6 +121,8 @@ public final class ThreadNetworkShellCommand extends BasicShellCommandHandler {
|
|||
pw.println(" Sets country code to <two-letter code> or left for normal value");
|
||||
pw.println(" ot-ctl <subcommand>");
|
||||
pw.println(" Runs ot-ctl command");
|
||||
pw.println(" config [name] [value]");
|
||||
pw.println(" Gets the config or sets the value for a config entry");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -144,6 +149,8 @@ public final class ThreadNetworkShellCommand extends BasicShellCommandHandler {
|
|||
return forceCountryCode();
|
||||
case "get-country-code":
|
||||
return getCountryCode();
|
||||
case "config":
|
||||
return handleConfigCommand();
|
||||
case "ot-ctl":
|
||||
return handleOtCtlCommand();
|
||||
default:
|
||||
|
|
@ -261,6 +268,68 @@ public final class ThreadNetworkShellCommand extends BasicShellCommandHandler {
|
|||
return 0;
|
||||
}
|
||||
|
||||
private int handleConfigCommand() {
|
||||
ensureTestingPermission();
|
||||
|
||||
// Get config
|
||||
if (peekNextArg() == null) {
|
||||
try {
|
||||
final ThreadConfiguration config = getConfig();
|
||||
getOutputWriter().println("Thread configuration = " + config);
|
||||
} catch (AssertionError e) {
|
||||
getErrorWriter().println("Failed: " + e.getMessage());
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Set config
|
||||
final String name = getNextArg();
|
||||
final String value = getNextArg();
|
||||
try {
|
||||
setConfig(name, value);
|
||||
} catch (AssertionError | IllegalArgumentException e) {
|
||||
getErrorWriter().println(e.getMessage());
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private ThreadConfiguration getConfig() throws AssertionError {
|
||||
final CompletableFuture<ThreadConfiguration> future = new CompletableFuture<>();
|
||||
mControllerService.registerConfigurationCallback(
|
||||
new IConfigurationReceiver.Stub() {
|
||||
@Override
|
||||
public void onConfigurationChanged(ThreadConfiguration config) {
|
||||
future.complete(config);
|
||||
}
|
||||
});
|
||||
try {
|
||||
return future.get(CONFIG_TIMEOUT.toSeconds(), TimeUnit.SECONDS);
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
throw new AssertionError("Failed to get config within timeout", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setConfig(String name, String value)
|
||||
throws IllegalArgumentException, AssertionError {
|
||||
if (name == null || value == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid config name = " + name + ", value=" + value);
|
||||
}
|
||||
final ThreadConfiguration oldConfig = getConfig();
|
||||
final ThreadConfiguration.Builder newConfigBuilder =
|
||||
new ThreadConfiguration.Builder(oldConfig);
|
||||
switch (name) {
|
||||
case "nat64" -> newConfigBuilder.setNat64Enabled(argEnabledOrDisabled(value));
|
||||
case "pd" -> newConfigBuilder.setDhcpv6PdEnabled(argEnabledOrDisabled(value));
|
||||
default -> throw new IllegalArgumentException("Invalid config name: " + name);
|
||||
}
|
||||
CompletableFuture<Void> future = new CompletableFuture();
|
||||
mControllerService.setConfiguration(newConfigBuilder.build(), newOperationReceiver(future));
|
||||
waitForFuture(future, CONFIG_TIMEOUT, mErrorWriter);
|
||||
}
|
||||
|
||||
private static final class OutputReceiver extends IOutputReceiver.Stub {
|
||||
private final CompletableFuture<Void> future;
|
||||
private final PrintWriter outputWriter;
|
||||
|
|
@ -359,6 +428,10 @@ public final class ThreadNetworkShellCommand extends BasicShellCommandHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private static boolean argEnabledOrDisabled(String arg) {
|
||||
return argTrueOrFalse(arg, "enabled", "disabled");
|
||||
}
|
||||
|
||||
private boolean getNextArgRequiredTrueOrFalse(String trueString, String falseString) {
|
||||
String nextArg = getNextArgRequired();
|
||||
return argTrueOrFalse(nextArg, trueString, falseString);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package android.net.thread;
|
|||
import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
|
||||
import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
|
||||
import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.DEFAULT_CONFIG;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.DEFAULT_DATASET;
|
||||
|
||||
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
|
||||
|
|
@ -79,6 +80,7 @@ public class ThreadNetworkShellCommandTest {
|
|||
public void tearDown() throws Exception {
|
||||
mFtd.destroy();
|
||||
ensureThreadEnabled();
|
||||
mController.setConfigurationAndWait(DEFAULT_CONFIG);
|
||||
}
|
||||
|
||||
private static void ensureThreadEnabled() {
|
||||
|
|
@ -179,6 +181,27 @@ public class ThreadNetworkShellCommandTest {
|
|||
assertThat(result).endsWith("Done\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void config_getConfig_expectedValueIsPrinted() throws Exception {
|
||||
ThreadConfiguration config =
|
||||
new ThreadConfiguration.Builder().setNat64Enabled(true).build();
|
||||
mController.setConfigurationAndWait(config);
|
||||
|
||||
final String result = runThreadCommand("config");
|
||||
|
||||
assertThat(result).contains("Nat64Enabled=true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void config_setConfig_expectedValueIsSet() throws Exception {
|
||||
ThreadConfiguration config = new ThreadConfiguration.Builder().build();
|
||||
mController.setConfigurationAndWait(config);
|
||||
|
||||
runThreadCommand("config nat64 enabled");
|
||||
|
||||
assertThat(mController.getConfiguration().isNat64Enabled()).isTrue();
|
||||
}
|
||||
|
||||
private static String runThreadCommand(String cmd) {
|
||||
return runShellCommandOrThrow("cmd thread_network " + cmd);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import android.net.TestNetworkInterface
|
|||
import android.net.nsd.NsdManager
|
||||
import android.net.nsd.NsdServiceInfo
|
||||
import android.net.thread.ActiveOperationalDataset
|
||||
import android.net.thread.ThreadConfiguration
|
||||
import android.net.thread.ThreadNetworkController
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
|
|
@ -108,6 +109,9 @@ object IntegrationTestUtils {
|
|||
val DEFAULT_DATASET: ActiveOperationalDataset =
|
||||
ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS)
|
||||
|
||||
@JvmField
|
||||
val DEFAULT_CONFIG = ThreadConfiguration.Builder().build()
|
||||
|
||||
/**
|
||||
* Waits for the given [Supplier] to be true until given timeout.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -38,8 +38,11 @@ import static org.mockito.Mockito.when;
|
|||
|
||||
import android.content.Context;
|
||||
import android.net.thread.ActiveOperationalDataset;
|
||||
import android.net.thread.IConfigurationReceiver;
|
||||
import android.net.thread.IOperationReceiver;
|
||||
import android.net.thread.IOutputReceiver;
|
||||
import android.net.thread.PendingOperationalDataset;
|
||||
import android.net.thread.ThreadConfiguration;
|
||||
import android.os.Binder;
|
||||
import android.os.Process;
|
||||
|
||||
|
|
@ -320,4 +323,108 @@ public class ThreadNetworkShellCommandTest {
|
|||
inOrder.verify(mOutputWriter).print("Done");
|
||||
inOrder.verify(mOutputWriter).print("\r\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void config_getConfig_testingPermissionIsChecked() {
|
||||
runShellCommand("config");
|
||||
|
||||
verify(mContext, times(1))
|
||||
.enforceCallingOrSelfPermission(
|
||||
eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void config_getConfig_serviceTimeOut_failsWithTimeoutError() {
|
||||
runShellCommand("config");
|
||||
|
||||
verify(mControllerService, times(1)).registerConfigurationCallback(any());
|
||||
verify(mErrorWriter, atLeastOnce()).println(contains("timeout"));
|
||||
verify(mOutputWriter, never()).println();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void config_getConfig_expectedValueIsPrinted() {
|
||||
doAnswer(
|
||||
inv -> {
|
||||
((IConfigurationReceiver) inv.getArgument(0))
|
||||
.onConfigurationChanged(
|
||||
new ThreadConfiguration.Builder()
|
||||
.setNat64Enabled(true)
|
||||
.build());
|
||||
return null;
|
||||
})
|
||||
.when(mControllerService)
|
||||
.registerConfigurationCallback(any());
|
||||
|
||||
runShellCommand("config");
|
||||
|
||||
verify(mErrorWriter, never()).println();
|
||||
verify(mOutputWriter, times(1)).println(contains("Nat64Enabled=true"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void config_setConfig_testingPermissionIsChecked() {
|
||||
runShellCommand("config", "nat64", "enabled");
|
||||
|
||||
verify(mContext, times(1))
|
||||
.enforceCallingOrSelfPermission(
|
||||
eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void config_setConfig_serviceTimeOut_failedWithTimeoutError() {
|
||||
runShellCommand("config", "nat64", "enabled");
|
||||
|
||||
verify(mControllerService, times(1)).registerConfigurationCallback(any());
|
||||
verify(mErrorWriter, atLeastOnce()).println(contains("timeout"));
|
||||
verify(mOutputWriter, never()).println();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void config_invalidArgument_failsWithInvalidArgumentError() {
|
||||
doAnswer(
|
||||
inv -> {
|
||||
((IConfigurationReceiver) inv.getArgument(0))
|
||||
.onConfigurationChanged(
|
||||
new ThreadConfiguration.Builder().build());
|
||||
return null;
|
||||
})
|
||||
.when(mControllerService)
|
||||
.registerConfigurationCallback(any());
|
||||
|
||||
runShellCommand("config", "invalidName", "invalidValue");
|
||||
|
||||
verify(mErrorWriter, atLeastOnce()).println(contains("Invalid config"));
|
||||
verify(mOutputWriter, never()).println();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void config_setConfig_expectedValueIsSet() {
|
||||
doAnswer(
|
||||
inv -> {
|
||||
((IConfigurationReceiver) inv.getArgument(0))
|
||||
.onConfigurationChanged(
|
||||
new ThreadConfiguration.Builder()
|
||||
.setNat64Enabled(false)
|
||||
.build());
|
||||
return null;
|
||||
})
|
||||
.when(mControllerService)
|
||||
.registerConfigurationCallback(any());
|
||||
doAnswer(
|
||||
inv -> {
|
||||
((IOperationReceiver) inv.getArgument(0)).onSuccess();
|
||||
return null;
|
||||
})
|
||||
.when(mControllerService)
|
||||
.setConfiguration(any(), any());
|
||||
|
||||
runShellCommand("config", "nat64", "enabled");
|
||||
|
||||
verify(mControllerService, times(1))
|
||||
.setConfiguration(
|
||||
eq(new ThreadConfiguration.Builder().setNat64Enabled(true).build()), any());
|
||||
verify(mErrorWriter, never()).println();
|
||||
verify(mOutputWriter, never()).println();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue