diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java index 7a129dca1b5d73794ced4e4bb134fa20a2f2b306..aab4b2e454f5056efe640a92fdd36d59438eeb9b 100644 --- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java +++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java @@ -39,6 +39,7 @@ import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; import static org.junit.Assume.assumeNotNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -66,6 +67,7 @@ import com.android.net.module.util.ArrayTrackRecord; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import com.android.testutils.DevSdkIgnoreRunner; +import com.android.testutils.FunctionalUtils.ThrowingRunnable; import org.junit.After; import org.junit.Before; @@ -95,288 +97,85 @@ public class ThreadNetworkControllerTest { private static final int NETWORK_CALLBACK_TIMEOUT_MILLIS = 10 * 1000; private static final int CALLBACK_TIMEOUT_MILLIS = 1_000; private static final int ENABLED_TIMEOUT_MILLIS = 2_000; - private static final String PERMISSION_THREAD_NETWORK_PRIVILEGED = + private static final String THREAD_NETWORK_PRIVILEGED = "android.permission.THREAD_NETWORK_PRIVILEGED"; @Rule public DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); private final Context mContext = ApplicationProvider.getApplicationContext(); private ExecutorService mExecutor; - private ThreadNetworkManager mManager; + private ThreadNetworkController mController; private Set<String> mGrantedPermissions; @Before public void setUp() throws Exception { - mExecutor = Executors.newSingleThreadExecutor(); - mManager = mContext.getSystemService(ThreadNetworkManager.class); + mGrantedPermissions = new HashSet<String>(); + mExecutor = Executors.newSingleThreadExecutor(); + ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class); + if (manager != null) { + mController = manager.getAllThreadNetworkControllers().get(0); + } // TODO: we will also need it in tearDown(), it's better to have a Rule to skip // tests if a feature is not available. - assumeNotNull(mManager); + assumeNotNull(mController); - for (ThreadNetworkController controller : getAllControllers()) { - setEnabledAndWait(controller, true); - } + setEnabledAndWait(mController, true); } @After public void tearDown() throws Exception { - if (mManager != null) { - dropAllPermissions(); - leaveAndWait(); - } - } - - private List<ThreadNetworkController> getAllControllers() { - return mManager.getAllThreadNetworkControllers(); - } - - private void leaveAndWait() throws Exception { - for (ThreadNetworkController controller : getAllControllers()) { + if (mController != null) { + grantPermissions(THREAD_NETWORK_PRIVILEGED); CompletableFuture<Void> future = new CompletableFuture<>(); - leave(controller, future::complete); + mController.leave(mExecutor, future::complete); future.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS); } - } - - private void grantPermissions(String... permissions) { - for (String permission : permissions) { - mGrantedPermissions.add(permission); - } - String[] allPermissions = new String[mGrantedPermissions.size()]; - mGrantedPermissions.toArray(allPermissions); - getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(allPermissions); - } - - private static void dropAllPermissions() { - getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); - } - - private static ActiveOperationalDataset newRandomizedDataset( - String networkName, ThreadNetworkController controller) throws Exception { - CompletableFuture<ActiveOperationalDataset> future = new CompletableFuture<>(); - controller.createRandomizedDataset(networkName, directExecutor(), future::complete); - return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); - } - - private static boolean isAttached(ThreadNetworkController controller) throws Exception { - return ThreadNetworkController.isAttached(getDeviceRole(controller)); - } - - private static int getDeviceRole(ThreadNetworkController controller) throws Exception { - CompletableFuture<Integer> future = new CompletableFuture<>(); - StateCallback callback = future::complete; - controller.registerStateCallback(directExecutor(), callback); - int role = future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); - controller.unregisterStateCallback(callback); - return role; - } - - private static int waitForAttachedState(ThreadNetworkController controller) throws Exception { - List<Integer> attachedRoles = new ArrayList<>(); - attachedRoles.add(DEVICE_ROLE_CHILD); - attachedRoles.add(DEVICE_ROLE_ROUTER); - attachedRoles.add(DEVICE_ROLE_LEADER); - return waitForStateAnyOf(controller, attachedRoles); - } - - private static int waitForStateAnyOf( - ThreadNetworkController controller, List<Integer> deviceRoles) throws Exception { - CompletableFuture<Integer> future = new CompletableFuture<>(); - StateCallback callback = - newRole -> { - if (deviceRoles.contains(newRole)) { - future.complete(newRole); - } - }; - controller.registerStateCallback(directExecutor(), callback); - int role = future.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); - controller.unregisterStateCallback(callback); - return role; - } - - private static void waitForEnabledState(ThreadNetworkController controller, int state) - throws Exception { - CompletableFuture<Integer> future = new CompletableFuture<>(); - StateCallback callback = - new ThreadNetworkController.StateCallback() { - @Override - public void onDeviceRoleChanged(int r) {} - - @Override - public void onThreadEnableStateChanged(int enabled) { - if (enabled == state) { - future.complete(enabled); - } - } - }; - runAsShell( - ACCESS_NETWORK_STATE, - () -> controller.registerStateCallback(directExecutor(), callback)); - future.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); - runAsShell(ACCESS_NETWORK_STATE, () -> controller.unregisterStateCallback(callback)); - } - - private void leave( - ThreadNetworkController controller, - OutcomeReceiver<Void, ThreadNetworkException> receiver) { - runAsShell( - PERMISSION_THREAD_NETWORK_PRIVILEGED, () -> controller.leave(mExecutor, receiver)); - } - - private void scheduleMigration( - ThreadNetworkController controller, - PendingOperationalDataset pendingDataset, - OutcomeReceiver<Void, ThreadNetworkException> receiver) { - runAsShell( - PERMISSION_THREAD_NETWORK_PRIVILEGED, - () -> controller.scheduleMigration(pendingDataset, mExecutor, receiver)); - } - - private class EnabledStateListener { - private ArrayTrackRecord<Integer> mEnabledStates = new ArrayTrackRecord<>(); - private final ArrayTrackRecord<Integer>.ReadHead mReadHead = mEnabledStates.newReadHead(); - ThreadNetworkController mController; - StateCallback mCallback = - new ThreadNetworkController.StateCallback() { - @Override - public void onDeviceRoleChanged(int r) {} - - @Override - public void onThreadEnableStateChanged(int enabled) { - mEnabledStates.add(enabled); - } - }; - - EnabledStateListener(ThreadNetworkController controller) { - this.mController = controller; - runAsShell( - ACCESS_NETWORK_STATE, - () -> controller.registerStateCallback(mExecutor, mCallback)); - } - - public void expectThreadEnabledState(int enabled) { - assertNotNull(mReadHead.poll(ENABLED_TIMEOUT_MILLIS, e -> (e == enabled))); - } - - public void unregisterStateCallback() { - runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(mCallback)); - } - } - - private int booleanToEnabledState(boolean enabled) { - return enabled ? STATE_ENABLED : STATE_DISABLED; - } - - private void setEnabledAndWait(ThreadNetworkController controller, boolean enabled) - throws Exception { - CompletableFuture<Void> setFuture = new CompletableFuture<>(); - runAsShell( - PERMISSION_THREAD_NETWORK_PRIVILEGED, - () -> controller.setEnabled(enabled, mExecutor, newOutcomeReceiver(setFuture))); - setFuture.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); - waitForEnabledState(controller, booleanToEnabledState(enabled)); - } - - private CompletableFuture joinRandomizedDataset(ThreadNetworkController controller) - throws Exception { - ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller); - CompletableFuture<Void> joinFuture = new CompletableFuture<>(); - runAsShell( - PERMISSION_THREAD_NETWORK_PRIVILEGED, - () -> controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture))); - return joinFuture; - } - - private void joinRandomizedDatasetAndWait(ThreadNetworkController controller) throws Exception { - CompletableFuture<Void> joinFuture = joinRandomizedDataset(controller); - joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); - runAsShell(ACCESS_NETWORK_STATE, () -> assertThat(isAttached(controller)).isTrue()); - } - - private static ActiveOperationalDataset getActiveOperationalDataset( - ThreadNetworkController controller) throws Exception { - CompletableFuture<ActiveOperationalDataset> future = new CompletableFuture<>(); - OperationalDatasetCallback callback = future::complete; - controller.registerOperationalDatasetCallback(directExecutor(), callback); - ActiveOperationalDataset dataset = future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); - controller.unregisterOperationalDatasetCallback(callback); - return dataset; - } - - private static PendingOperationalDataset getPendingOperationalDataset( - ThreadNetworkController controller) throws Exception { - CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>(); - CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>(); - controller.registerOperationalDatasetCallback( - directExecutor(), newDatasetCallback(activeFuture, pendingFuture)); - return pendingFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); - } - - private static OperationalDatasetCallback newDatasetCallback( - CompletableFuture<ActiveOperationalDataset> activeFuture, - CompletableFuture<PendingOperationalDataset> pendingFuture) { - return new OperationalDatasetCallback() { - @Override - public void onActiveOperationalDatasetChanged( - ActiveOperationalDataset activeOpDataset) { - activeFuture.complete(activeOpDataset); - } - - @Override - public void onPendingOperationalDatasetChanged( - PendingOperationalDataset pendingOpDataset) { - pendingFuture.complete(pendingOpDataset); - } - }; + dropAllPermissions(); } @Test public void getThreadVersion_returnsAtLeastThreadVersion1P3() { - for (ThreadNetworkController controller : getAllControllers()) { - assertThat(controller.getThreadVersion()).isAtLeast(THREAD_VERSION_1_3); - } + assertThat(mController.getThreadVersion()).isAtLeast(THREAD_VERSION_1_3); } @Test public void registerStateCallback_permissionsGranted_returnsCurrentStates() throws Exception { - grantPermissions(ACCESS_NETWORK_STATE); + CompletableFuture<Integer> deviceRole = new CompletableFuture<>(); + StateCallback callback = deviceRole::complete; - for (ThreadNetworkController controller : getAllControllers()) { - CompletableFuture<Integer> deviceRole = new CompletableFuture<>(); - StateCallback callback = deviceRole::complete; - - try { - controller.registerStateCallback(mExecutor, callback); + try { + runAsShell( + ACCESS_NETWORK_STATE, + () -> mController.registerStateCallback(mExecutor, callback)); - assertThat(deviceRole.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)) - .isEqualTo(DEVICE_ROLE_STOPPED); - } finally { - controller.unregisterStateCallback(callback); - } + assertThat(deviceRole.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)) + .isEqualTo(DEVICE_ROLE_STOPPED); + } finally { + runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback)); } } @Test public void registerStateCallback_returnsUpdatedEnabledStates() throws Exception { - for (ThreadNetworkController controller : getAllControllers()) { - CompletableFuture<Void> setFuture1 = new CompletableFuture<>(); - CompletableFuture<Void> setFuture2 = new CompletableFuture<>(); - EnabledStateListener listener = new EnabledStateListener(controller); + CompletableFuture<Void> setFuture1 = new CompletableFuture<>(); + CompletableFuture<Void> setFuture2 = new CompletableFuture<>(); + EnabledStateListener listener = new EnabledStateListener(mController); + try { runAsShell( - PERMISSION_THREAD_NETWORK_PRIVILEGED, + THREAD_NETWORK_PRIVILEGED, () -> { - controller.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture1)); + mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture1)); }); setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); runAsShell( - PERMISSION_THREAD_NETWORK_PRIVILEGED, + THREAD_NETWORK_PRIVILEGED, () -> { - controller.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture2)); + mController.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture2)); }); setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); @@ -384,7 +183,7 @@ public class ThreadNetworkControllerTest { listener.expectThreadEnabledState(STATE_DISABLING); listener.expectThreadEnabledState(STATE_DISABLED); listener.expectThreadEnabledState(STATE_ENABLED); - + } finally { listener.unregisterStateCallback(); } } @@ -393,108 +192,91 @@ public class ThreadNetworkControllerTest { public void registerStateCallback_noPermissions_throwsSecurityException() throws Exception { dropAllPermissions(); - for (ThreadNetworkController controller : getAllControllers()) { - assertThrows( - SecurityException.class, - () -> controller.registerStateCallback(mExecutor, role -> {})); - } + assertThrows( + SecurityException.class, + () -> mController.registerStateCallback(mExecutor, role -> {})); } @Test public void registerStateCallback_alreadyRegistered_throwsIllegalArgumentException() throws Exception { grantPermissions(ACCESS_NETWORK_STATE); + CompletableFuture<Integer> deviceRole = new CompletableFuture<>(); + StateCallback callback = role -> deviceRole.complete(role); - for (ThreadNetworkController controller : getAllControllers()) { - CompletableFuture<Integer> deviceRole = new CompletableFuture<>(); - StateCallback callback = role -> deviceRole.complete(role); - controller.registerStateCallback(mExecutor, callback); + mController.registerStateCallback(mExecutor, callback); - assertThrows( - IllegalArgumentException.class, - () -> controller.registerStateCallback(mExecutor, callback)); - } + assertThrows( + IllegalArgumentException.class, + () -> mController.registerStateCallback(mExecutor, callback)); } @Test public void unregisterStateCallback_noPermissions_throwsSecurityException() throws Exception { - for (ThreadNetworkController controller : getAllControllers()) { - CompletableFuture<Integer> deviceRole = new CompletableFuture<>(); - StateCallback callback = role -> deviceRole.complete(role); - grantPermissions(ACCESS_NETWORK_STATE); - controller.registerStateCallback(mExecutor, callback); - - try { - dropAllPermissions(); - assertThrows( - SecurityException.class, - () -> controller.unregisterStateCallback(callback)); - } finally { - grantPermissions(ACCESS_NETWORK_STATE); - controller.unregisterStateCallback(callback); - } + CompletableFuture<Integer> deviceRole = new CompletableFuture<>(); + StateCallback callback = role -> deviceRole.complete(role); + runAsShell( + ACCESS_NETWORK_STATE, () -> mController.registerStateCallback(mExecutor, callback)); + + try { + dropAllPermissions(); + assertThrows( + SecurityException.class, () -> mController.unregisterStateCallback(callback)); + } finally { + runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback)); } } @Test public void unregisterStateCallback_callbackRegistered_success() throws Exception { grantPermissions(ACCESS_NETWORK_STATE); - for (ThreadNetworkController controller : getAllControllers()) { - CompletableFuture<Integer> deviceRole = new CompletableFuture<>(); - StateCallback callback = role -> deviceRole.complete(role); - controller.registerStateCallback(mExecutor, callback); + CompletableFuture<Integer> deviceRole = new CompletableFuture<>(); + StateCallback callback = role -> deviceRole.complete(role); - controller.unregisterStateCallback(callback); - } + assertDoesNotThrow(() -> mController.registerStateCallback(mExecutor, callback)); + mController.unregisterStateCallback(callback); } @Test public void unregisterStateCallback_callbackNotRegistered_throwsIllegalArgumentException() throws Exception { - for (ThreadNetworkController controller : getAllControllers()) { - CompletableFuture<Integer> deviceRole = new CompletableFuture<>(); - StateCallback callback = role -> deviceRole.complete(role); + CompletableFuture<Integer> deviceRole = new CompletableFuture<>(); + StateCallback callback = role -> deviceRole.complete(role); - assertThrows( - IllegalArgumentException.class, - () -> controller.unregisterStateCallback(callback)); - } + assertThrows( + IllegalArgumentException.class, + () -> mController.unregisterStateCallback(callback)); } @Test public void unregisterStateCallback_alreadyUnregistered_throwsIllegalArgumentException() throws Exception { grantPermissions(ACCESS_NETWORK_STATE); - for (ThreadNetworkController controller : getAllControllers()) { - CompletableFuture<Integer> deviceRole = new CompletableFuture<>(); - StateCallback callback = deviceRole::complete; - controller.registerStateCallback(mExecutor, callback); - controller.unregisterStateCallback(callback); + CompletableFuture<Integer> deviceRole = new CompletableFuture<>(); + StateCallback callback = deviceRole::complete; + mController.registerStateCallback(mExecutor, callback); + mController.unregisterStateCallback(callback); - assertThrows( - IllegalArgumentException.class, - () -> controller.unregisterStateCallback(callback)); - } + assertThrows( + IllegalArgumentException.class, + () -> mController.unregisterStateCallback(callback)); } @Test public void registerOperationalDatasetCallback_permissionsGranted_returnsCurrentStates() throws Exception { - grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED); - - for (ThreadNetworkController controller : getAllControllers()) { - CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>(); - CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>(); - var callback = newDatasetCallback(activeFuture, pendingFuture); + grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED); + CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>(); + CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>(); + var callback = newDatasetCallback(activeFuture, pendingFuture); - try { - controller.registerOperationalDatasetCallback(mExecutor, callback); + try { + mController.registerOperationalDatasetCallback(mExecutor, callback); - assertThat(activeFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull(); - assertThat(pendingFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull(); - } finally { - controller.unregisterOperationalDatasetCallback(callback); - } + assertThat(activeFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull(); + assertThat(pendingFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull(); + } finally { + mController.unregisterOperationalDatasetCallback(callback); } } @@ -502,52 +284,47 @@ public class ThreadNetworkControllerTest { public void registerOperationalDatasetCallback_noPermissions_throwsSecurityException() throws Exception { dropAllPermissions(); + CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>(); + CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>(); + var callback = newDatasetCallback(activeFuture, pendingFuture); - for (ThreadNetworkController controller : getAllControllers()) { - CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>(); - CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>(); - var callback = newDatasetCallback(activeFuture, pendingFuture); - - assertThrows( - SecurityException.class, - () -> controller.registerOperationalDatasetCallback(mExecutor, callback)); - } + assertThrows( + SecurityException.class, + () -> mController.registerOperationalDatasetCallback(mExecutor, callback)); } @Test public void unregisterOperationalDatasetCallback_callbackRegistered_success() throws Exception { - grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED); - for (ThreadNetworkController controller : getAllControllers()) { - CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>(); - CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>(); - var callback = newDatasetCallback(activeFuture, pendingFuture); - controller.registerOperationalDatasetCallback(mExecutor, callback); - - controller.unregisterOperationalDatasetCallback(callback); - } + grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED); + CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>(); + CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>(); + var callback = newDatasetCallback(activeFuture, pendingFuture); + mController.registerOperationalDatasetCallback(mExecutor, callback); + + assertDoesNotThrow(() -> mController.unregisterOperationalDatasetCallback(callback)); } @Test public void unregisterOperationalDatasetCallback_noPermissions_throwsSecurityException() throws Exception { - dropAllPermissions(); - - for (ThreadNetworkController controller : getAllControllers()) { - CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>(); - CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>(); - var callback = newDatasetCallback(activeFuture, pendingFuture); - grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED); - controller.registerOperationalDatasetCallback(mExecutor, callback); + CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>(); + CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>(); + var callback = newDatasetCallback(activeFuture, pendingFuture); + runAsShell( + ACCESS_NETWORK_STATE, + THREAD_NETWORK_PRIVILEGED, + () -> mController.registerOperationalDatasetCallback(mExecutor, callback)); - try { - dropAllPermissions(); - assertThrows( - SecurityException.class, - () -> controller.unregisterOperationalDatasetCallback(callback)); - } finally { - grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED); - controller.unregisterOperationalDatasetCallback(callback); - } + try { + dropAllPermissions(); + assertThrows( + SecurityException.class, + () -> mController.unregisterOperationalDatasetCallback(callback)); + } finally { + runAsShell( + ACCESS_NETWORK_STATE, + THREAD_NETWORK_PRIVILEGED, + () -> mController.unregisterOperationalDatasetCallback(callback)); } } @@ -568,485 +345,431 @@ public class ThreadNetworkControllerTest { @Test public void join_withPrivilegedPermission_success() throws Exception { - grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED); - - for (ThreadNetworkController controller : getAllControllers()) { - ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller); - CompletableFuture<Void> joinFuture = new CompletableFuture<>(); + ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController); + CompletableFuture<Void> joinFuture = new CompletableFuture<>(); - controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)); - joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture))); + joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); - grantPermissions(ACCESS_NETWORK_STATE); - assertThat(isAttached(controller)).isTrue(); - assertThat(getActiveOperationalDataset(controller)).isEqualTo(activeDataset); - } + assertThat(isAttached(mController)).isTrue(); + assertThat(getActiveOperationalDataset(mController)).isEqualTo(activeDataset); } @Test public void join_withoutPrivilegedPermission_throwsSecurityException() throws Exception { dropAllPermissions(); + ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController); - for (ThreadNetworkController controller : getAllControllers()) { - ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller); - - assertThrows( - SecurityException.class, - () -> controller.join(activeDataset, mExecutor, v -> {})); - } + assertThrows( + SecurityException.class, () -> mController.join(activeDataset, mExecutor, v -> {})); } @Test public void join_threadDisabled_failsWithErrorThreadDisabled() throws Exception { - for (ThreadNetworkController controller : getAllControllers()) { - setEnabledAndWait(controller, false); + setEnabledAndWait(mController, false); + ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController); + CompletableFuture<Void> joinFuture = new CompletableFuture<>(); - CompletableFuture<Void> joinFuture = joinRandomizedDataset(controller); + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture))); - ThreadNetworkException thrown = - (ThreadNetworkException) - assertThrows(ExecutionException.class, joinFuture::get).getCause(); - assertThat(thrown.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED); - } + var thrown = + assertThrows( + ExecutionException.class, + () -> joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS)); + var threadException = (ThreadNetworkException) thrown.getCause(); + assertThat(threadException.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED); } @Test public void join_concurrentRequests_firstOneIsAborted() throws Exception { - grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED); - final byte[] KEY_1 = new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; final byte[] KEY_2 = new byte[] {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}; - for (ThreadNetworkController controller : getAllControllers()) { - ActiveOperationalDataset activeDataset1 = - new ActiveOperationalDataset.Builder( - newRandomizedDataset("TestNet", controller)) - .setNetworkKey(KEY_1) - .build(); - ActiveOperationalDataset activeDataset2 = - new ActiveOperationalDataset.Builder(activeDataset1) - .setNetworkKey(KEY_2) - .build(); - CompletableFuture<Void> joinFuture1 = new CompletableFuture<>(); - CompletableFuture<Void> joinFuture2 = new CompletableFuture<>(); - - controller.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture1)); - controller.join(activeDataset2, mExecutor, newOutcomeReceiver(joinFuture2)); - - ThreadNetworkException thrown = - (ThreadNetworkException) - assertThrows(ExecutionException.class, joinFuture1::get).getCause(); - assertThat(thrown.getErrorCode()).isEqualTo(ERROR_ABORTED); - joinFuture2.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); - grantPermissions(ACCESS_NETWORK_STATE); - assertThat(isAttached(controller)).isTrue(); - assertThat(getActiveOperationalDataset(controller)).isEqualTo(activeDataset2); - } + ActiveOperationalDataset activeDataset1 = + new ActiveOperationalDataset.Builder(newRandomizedDataset("TestNet", mController)) + .setNetworkKey(KEY_1) + .build(); + ActiveOperationalDataset activeDataset2 = + new ActiveOperationalDataset.Builder(activeDataset1).setNetworkKey(KEY_2).build(); + CompletableFuture<Void> joinFuture1 = new CompletableFuture<>(); + CompletableFuture<Void> joinFuture2 = new CompletableFuture<>(); + + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> { + mController.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture1)); + mController.join(activeDataset2, mExecutor, newOutcomeReceiver(joinFuture2)); + }); + + var thrown = + assertThrows( + ExecutionException.class, + () -> joinFuture1.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS)); + var threadException = (ThreadNetworkException) thrown.getCause(); + assertThat(threadException.getErrorCode()).isEqualTo(ERROR_ABORTED); + joinFuture2.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); + assertThat(isAttached(mController)).isTrue(); + assertThat(getActiveOperationalDataset(mController)).isEqualTo(activeDataset2); } @Test public void leave_withPrivilegedPermission_success() throws Exception { - for (ThreadNetworkController controller : getAllControllers()) { - joinRandomizedDatasetAndWait(controller); + CompletableFuture<Void> leaveFuture = new CompletableFuture<>(); + joinRandomizedDatasetAndWait(mController); - CompletableFuture<Void> leaveFuture = new CompletableFuture<>(); - leave(controller, newOutcomeReceiver(leaveFuture)); - leaveFuture.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS); + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> mController.leave(mExecutor, newOutcomeReceiver(leaveFuture))); + leaveFuture.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS); - grantPermissions(ACCESS_NETWORK_STATE); - assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED); - } + assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED); } @Test public void leave_withoutPrivilegedPermission_throwsSecurityException() { dropAllPermissions(); - for (ThreadNetworkController controller : getAllControllers()) { - assertThrows(SecurityException.class, () -> controller.leave(mExecutor, v -> {})); - } + assertThrows(SecurityException.class, () -> mController.leave(mExecutor, v -> {})); } @Test public void leave_threadDisabled_success() throws Exception { - for (ThreadNetworkController controller : getAllControllers()) { - joinRandomizedDatasetAndWait(controller); + setEnabledAndWait(mController, false); + CompletableFuture<Void> leaveFuture = new CompletableFuture<>(); - CompletableFuture<Void> leaveFuture = new CompletableFuture<>(); - setEnabledAndWait(controller, false); - leave(controller, newOutcomeReceiver(leaveFuture)); + leave(mController, newOutcomeReceiver(leaveFuture)); + leaveFuture.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS); - leaveFuture.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS); - runAsShell( - ACCESS_NETWORK_STATE, - () -> assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED)); - } + assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED); } @Test public void leave_concurrentRequests_bothSuccess() throws Exception { - grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED); - - for (ThreadNetworkController controller : getAllControllers()) { - ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller); - CompletableFuture<Void> joinFuture = new CompletableFuture<>(); - CompletableFuture<Void> leaveFuture1 = new CompletableFuture<>(); - CompletableFuture<Void> leaveFuture2 = new CompletableFuture<>(); - controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)); - joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); - - controller.leave(mExecutor, newOutcomeReceiver(leaveFuture1)); - controller.leave(mExecutor, newOutcomeReceiver(leaveFuture2)); - - leaveFuture1.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS); - leaveFuture2.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS); - grantPermissions(ACCESS_NETWORK_STATE); - assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED); - } + CompletableFuture<Void> leaveFuture1 = new CompletableFuture<>(); + CompletableFuture<Void> leaveFuture2 = new CompletableFuture<>(); + joinRandomizedDatasetAndWait(mController); + + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> { + mController.leave(mExecutor, newOutcomeReceiver(leaveFuture1)); + mController.leave(mExecutor, newOutcomeReceiver(leaveFuture2)); + }); + + leaveFuture1.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS); + leaveFuture2.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS); + assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED); } @Test public void scheduleMigration_withPrivilegedPermission_newDatasetApplied() throws Exception { - grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED); - - for (ThreadNetworkController controller : getAllControllers()) { - ActiveOperationalDataset activeDataset1 = - new ActiveOperationalDataset.Builder( - newRandomizedDataset("TestNet", controller)) - .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false)) - .setExtendedPanId(new byte[] {1, 1, 1, 1, 1, 1, 1, 1}) - .build(); - ActiveOperationalDataset activeDataset2 = - new ActiveOperationalDataset.Builder(activeDataset1) - .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false)) - .setNetworkName("ThreadNet2") - .build(); - PendingOperationalDataset pendingDataset2 = - new PendingOperationalDataset( - activeDataset2, - OperationalDatasetTimestamp.fromInstant(Instant.now()), - Duration.ofSeconds(30)); - CompletableFuture<Void> joinFuture = new CompletableFuture<>(); - CompletableFuture<Void> migrateFuture = new CompletableFuture<>(); - controller.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture)); - joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); - - controller.scheduleMigration( - pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture)); - migrateFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); - - CompletableFuture<Boolean> dataset2IsApplied = new CompletableFuture<>(); - CompletableFuture<Boolean> pendingDatasetIsRemoved = new CompletableFuture<>(); - OperationalDatasetCallback datasetCallback = - new OperationalDatasetCallback() { - @Override - public void onActiveOperationalDatasetChanged( - ActiveOperationalDataset activeDataset) { - if (activeDataset.equals(activeDataset2)) { - dataset2IsApplied.complete(true); - } + grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED); + ActiveOperationalDataset activeDataset1 = + new ActiveOperationalDataset.Builder(newRandomizedDataset("TestNet", mController)) + .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false)) + .setExtendedPanId(new byte[] {1, 1, 1, 1, 1, 1, 1, 1}) + .build(); + ActiveOperationalDataset activeDataset2 = + new ActiveOperationalDataset.Builder(activeDataset1) + .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false)) + .setNetworkName("ThreadNet2") + .build(); + PendingOperationalDataset pendingDataset2 = + new PendingOperationalDataset( + activeDataset2, + OperationalDatasetTimestamp.fromInstant(Instant.now()), + Duration.ofSeconds(30)); + CompletableFuture<Void> joinFuture = new CompletableFuture<>(); + CompletableFuture<Void> migrateFuture = new CompletableFuture<>(); + mController.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture)); + joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); + + mController.scheduleMigration( + pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture)); + migrateFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); + + CompletableFuture<Boolean> dataset2IsApplied = new CompletableFuture<>(); + CompletableFuture<Boolean> pendingDatasetIsRemoved = new CompletableFuture<>(); + OperationalDatasetCallback datasetCallback = + new OperationalDatasetCallback() { + @Override + public void onActiveOperationalDatasetChanged( + ActiveOperationalDataset activeDataset) { + if (activeDataset.equals(activeDataset2)) { + dataset2IsApplied.complete(true); } + } - @Override - public void onPendingOperationalDatasetChanged( - PendingOperationalDataset pendingDataset) { - if (pendingDataset == null) { - pendingDatasetIsRemoved.complete(true); - } + @Override + public void onPendingOperationalDatasetChanged( + PendingOperationalDataset pendingDataset) { + if (pendingDataset == null) { + pendingDatasetIsRemoved.complete(true); } - }; - controller.registerOperationalDatasetCallback(directExecutor(), datasetCallback); + } + }; + mController.registerOperationalDatasetCallback(directExecutor(), datasetCallback); + try { assertThat(dataset2IsApplied.get(MIGRATION_TIMEOUT_MILLIS, MILLISECONDS)).isTrue(); assertThat(pendingDatasetIsRemoved.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isTrue(); - controller.unregisterOperationalDatasetCallback(datasetCallback); + } finally { + mController.unregisterOperationalDatasetCallback(datasetCallback); } } @Test public void scheduleMigration_whenNotAttached_failWithPreconditionError() throws Exception { - grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED); - - for (ThreadNetworkController controller : getAllControllers()) { - PendingOperationalDataset pendingDataset = - new PendingOperationalDataset( - newRandomizedDataset("TestNet", controller), - OperationalDatasetTimestamp.fromInstant(Instant.now()), - Duration.ofSeconds(30)); - CompletableFuture<Void> migrateFuture = new CompletableFuture<>(); - - controller.scheduleMigration( - pendingDataset, mExecutor, newOutcomeReceiver(migrateFuture)); - - ThreadNetworkException thrown = - (ThreadNetworkException) - assertThrows(ExecutionException.class, migrateFuture::get).getCause(); - assertThat(thrown.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION); - } + grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED); + PendingOperationalDataset pendingDataset = + new PendingOperationalDataset( + newRandomizedDataset("TestNet", mController), + OperationalDatasetTimestamp.fromInstant(Instant.now()), + Duration.ofSeconds(30)); + CompletableFuture<Void> migrateFuture = new CompletableFuture<>(); + + mController.scheduleMigration(pendingDataset, mExecutor, newOutcomeReceiver(migrateFuture)); + + ThreadNetworkException thrown = + (ThreadNetworkException) + assertThrows(ExecutionException.class, migrateFuture::get).getCause(); + assertThat(thrown.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION); } @Test public void scheduleMigration_secondRequestHasSmallerTimestamp_rejectedByLeader() throws Exception { - grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED); - - for (ThreadNetworkController controller : getAllControllers()) { - final ActiveOperationalDataset activeDataset = - new ActiveOperationalDataset.Builder( - newRandomizedDataset("testNet", controller)) - .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false)) - .build(); - ActiveOperationalDataset activeDataset1 = - new ActiveOperationalDataset.Builder(activeDataset) - .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false)) - .setNetworkName("testNet1") - .build(); - PendingOperationalDataset pendingDataset1 = - new PendingOperationalDataset( - activeDataset1, - new OperationalDatasetTimestamp(100, 0, false), - Duration.ofSeconds(30)); - ActiveOperationalDataset activeDataset2 = - new ActiveOperationalDataset.Builder(activeDataset) - .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false)) - .setNetworkName("testNet2") - .build(); - PendingOperationalDataset pendingDataset2 = - new PendingOperationalDataset( - activeDataset2, - new OperationalDatasetTimestamp(20, 0, false), - Duration.ofSeconds(30)); - CompletableFuture<Void> joinFuture = new CompletableFuture<>(); - CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>(); - CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>(); - controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)); - joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); - - controller.scheduleMigration( - pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1)); - migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); - controller.scheduleMigration( - pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2)); - - ThreadNetworkException thrown = - (ThreadNetworkException) - assertThrows(ExecutionException.class, migrateFuture2::get).getCause(); - assertThat(thrown.getErrorCode()).isEqualTo(ERROR_REJECTED_BY_PEER); - } + grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED); + final ActiveOperationalDataset activeDataset = + new ActiveOperationalDataset.Builder(newRandomizedDataset("testNet", mController)) + .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false)) + .build(); + ActiveOperationalDataset activeDataset1 = + new ActiveOperationalDataset.Builder(activeDataset) + .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false)) + .setNetworkName("testNet1") + .build(); + PendingOperationalDataset pendingDataset1 = + new PendingOperationalDataset( + activeDataset1, + new OperationalDatasetTimestamp(100, 0, false), + Duration.ofSeconds(30)); + ActiveOperationalDataset activeDataset2 = + new ActiveOperationalDataset.Builder(activeDataset) + .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false)) + .setNetworkName("testNet2") + .build(); + PendingOperationalDataset pendingDataset2 = + new PendingOperationalDataset( + activeDataset2, + new OperationalDatasetTimestamp(20, 0, false), + Duration.ofSeconds(30)); + CompletableFuture<Void> joinFuture = new CompletableFuture<>(); + CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>(); + CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>(); + mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)); + joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); + + mController.scheduleMigration( + pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1)); + migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); + mController.scheduleMigration( + pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2)); + + ThreadNetworkException thrown = + (ThreadNetworkException) + assertThrows(ExecutionException.class, migrateFuture2::get).getCause(); + assertThat(thrown.getErrorCode()).isEqualTo(ERROR_REJECTED_BY_PEER); } @Test public void scheduleMigration_secondRequestHasLargerTimestamp_newDatasetApplied() throws Exception { - grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED); - - for (ThreadNetworkController controller : getAllControllers()) { - final ActiveOperationalDataset activeDataset = - new ActiveOperationalDataset.Builder( - newRandomizedDataset("validName", controller)) - .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false)) - .build(); - ActiveOperationalDataset activeDataset1 = - new ActiveOperationalDataset.Builder(activeDataset) - .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false)) - .setNetworkName("testNet1") - .build(); - PendingOperationalDataset pendingDataset1 = - new PendingOperationalDataset( - activeDataset1, - new OperationalDatasetTimestamp(100, 0, false), - Duration.ofSeconds(30)); - ActiveOperationalDataset activeDataset2 = - new ActiveOperationalDataset.Builder(activeDataset) - .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false)) - .setNetworkName("testNet2") - .build(); - PendingOperationalDataset pendingDataset2 = - new PendingOperationalDataset( - activeDataset2, - new OperationalDatasetTimestamp(200, 0, false), - Duration.ofSeconds(30)); - CompletableFuture<Void> joinFuture = new CompletableFuture<>(); - CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>(); - CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>(); - controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)); - joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); - - controller.scheduleMigration( - pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1)); - migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); - controller.scheduleMigration( - pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2)); - migrateFuture2.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); - - CompletableFuture<Boolean> dataset2IsApplied = new CompletableFuture<>(); - CompletableFuture<Boolean> pendingDatasetIsRemoved = new CompletableFuture<>(); - OperationalDatasetCallback datasetCallback = - new OperationalDatasetCallback() { - @Override - public void onActiveOperationalDatasetChanged( - ActiveOperationalDataset activeDataset) { - if (activeDataset.equals(activeDataset2)) { - dataset2IsApplied.complete(true); - } + grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED); + final ActiveOperationalDataset activeDataset = + new ActiveOperationalDataset.Builder(newRandomizedDataset("validName", mController)) + .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false)) + .build(); + ActiveOperationalDataset activeDataset1 = + new ActiveOperationalDataset.Builder(activeDataset) + .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false)) + .setNetworkName("testNet1") + .build(); + PendingOperationalDataset pendingDataset1 = + new PendingOperationalDataset( + activeDataset1, + new OperationalDatasetTimestamp(100, 0, false), + Duration.ofSeconds(30)); + ActiveOperationalDataset activeDataset2 = + new ActiveOperationalDataset.Builder(activeDataset) + .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false)) + .setNetworkName("testNet2") + .build(); + PendingOperationalDataset pendingDataset2 = + new PendingOperationalDataset( + activeDataset2, + new OperationalDatasetTimestamp(200, 0, false), + Duration.ofSeconds(30)); + CompletableFuture<Void> joinFuture = new CompletableFuture<>(); + CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>(); + CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>(); + mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)); + joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); + + mController.scheduleMigration( + pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1)); + migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); + mController.scheduleMigration( + pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2)); + migrateFuture2.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); + + CompletableFuture<Boolean> dataset2IsApplied = new CompletableFuture<>(); + CompletableFuture<Boolean> pendingDatasetIsRemoved = new CompletableFuture<>(); + OperationalDatasetCallback datasetCallback = + new OperationalDatasetCallback() { + @Override + public void onActiveOperationalDatasetChanged( + ActiveOperationalDataset activeDataset) { + if (activeDataset.equals(activeDataset2)) { + dataset2IsApplied.complete(true); } + } - @Override - public void onPendingOperationalDatasetChanged( - PendingOperationalDataset pendingDataset) { - if (pendingDataset == null) { - pendingDatasetIsRemoved.complete(true); - } + @Override + public void onPendingOperationalDatasetChanged( + PendingOperationalDataset pendingDataset) { + if (pendingDataset == null) { + pendingDatasetIsRemoved.complete(true); } - }; - controller.registerOperationalDatasetCallback(directExecutor(), datasetCallback); + } + }; + mController.registerOperationalDatasetCallback(directExecutor(), datasetCallback); + try { assertThat(dataset2IsApplied.get(MIGRATION_TIMEOUT_MILLIS, MILLISECONDS)).isTrue(); assertThat(pendingDatasetIsRemoved.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isTrue(); - controller.unregisterOperationalDatasetCallback(datasetCallback); + } finally { + mController.unregisterOperationalDatasetCallback(datasetCallback); } } @Test public void scheduleMigration_threadDisabled_failsWithErrorThreadDisabled() throws Exception { - for (ThreadNetworkController controller : getAllControllers()) { - ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller); - PendingOperationalDataset pendingDataset = - new PendingOperationalDataset( - activeDataset, - OperationalDatasetTimestamp.fromInstant(Instant.now()), - Duration.ofSeconds(30)); - joinRandomizedDatasetAndWait(controller); - CompletableFuture<Void> migrationFuture = new CompletableFuture<>(); - - setEnabledAndWait(controller, false); - - scheduleMigration(controller, pendingDataset, newOutcomeReceiver(migrationFuture)); - - ThreadNetworkException thrown = - (ThreadNetworkException) - assertThrows(ExecutionException.class, migrationFuture::get).getCause(); - assertThat(thrown.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED); - } + ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController); + PendingOperationalDataset pendingDataset = + new PendingOperationalDataset( + activeDataset, + OperationalDatasetTimestamp.fromInstant(Instant.now()), + Duration.ofSeconds(30)); + joinRandomizedDatasetAndWait(mController); + CompletableFuture<Void> migrationFuture = new CompletableFuture<>(); + + setEnabledAndWait(mController, false); + + scheduleMigration(mController, pendingDataset, newOutcomeReceiver(migrationFuture)); + + ThreadNetworkException thrown = + (ThreadNetworkException) + assertThrows(ExecutionException.class, migrationFuture::get).getCause(); + assertThat(thrown.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED); } @Test public void createRandomizedDataset_wrongNetworkNameLength_throwsIllegalArgumentException() { - for (ThreadNetworkController controller : getAllControllers()) { - assertThrows( - IllegalArgumentException.class, - () -> controller.createRandomizedDataset("", mExecutor, dataset -> {})); + assertThrows( + IllegalArgumentException.class, + () -> mController.createRandomizedDataset("", mExecutor, dataset -> {})); - assertThrows( - IllegalArgumentException.class, - () -> - controller.createRandomizedDataset( - "ANetNameIs17Bytes", mExecutor, dataset -> {})); - } + assertThrows( + IllegalArgumentException.class, + () -> + mController.createRandomizedDataset( + "ANetNameIs17Bytes", mExecutor, dataset -> {})); } @Test public void createRandomizedDataset_validNetworkName_success() throws Exception { - for (ThreadNetworkController controller : getAllControllers()) { - ActiveOperationalDataset dataset = newRandomizedDataset("validName", controller); - - assertThat(dataset.getNetworkName()).isEqualTo("validName"); - assertThat(dataset.getPanId()).isLessThan(0xffff); - assertThat(dataset.getChannelMask().size()).isAtLeast(1); - assertThat(dataset.getExtendedPanId()).hasLength(8); - assertThat(dataset.getNetworkKey()).hasLength(16); - assertThat(dataset.getPskc()).hasLength(16); - assertThat(dataset.getMeshLocalPrefix().getPrefixLength()).isEqualTo(64); - assertThat(dataset.getMeshLocalPrefix().getRawAddress()[0]).isEqualTo((byte) 0xfd); - } + ActiveOperationalDataset dataset = newRandomizedDataset("validName", mController); + + assertThat(dataset.getNetworkName()).isEqualTo("validName"); + assertThat(dataset.getPanId()).isLessThan(0xffff); + assertThat(dataset.getChannelMask().size()).isAtLeast(1); + assertThat(dataset.getExtendedPanId()).hasLength(8); + assertThat(dataset.getNetworkKey()).hasLength(16); + assertThat(dataset.getPskc()).hasLength(16); + assertThat(dataset.getMeshLocalPrefix().getPrefixLength()).isEqualTo(64); + assertThat(dataset.getMeshLocalPrefix().getRawAddress()[0]).isEqualTo((byte) 0xfd); } @Test public void setEnabled_permissionsGranted_succeeds() throws Exception { - for (ThreadNetworkController controller : getAllControllers()) { - CompletableFuture<Void> setFuture1 = new CompletableFuture<>(); - CompletableFuture<Void> setFuture2 = new CompletableFuture<>(); + CompletableFuture<Void> setFuture1 = new CompletableFuture<>(); + CompletableFuture<Void> setFuture2 = new CompletableFuture<>(); - runAsShell( - PERMISSION_THREAD_NETWORK_PRIVILEGED, - () -> controller.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture1))); - setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); - waitForEnabledState(controller, booleanToEnabledState(false)); + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture1))); + setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); + waitForEnabledState(mController, booleanToEnabledState(false)); - runAsShell( - PERMISSION_THREAD_NETWORK_PRIVILEGED, - () -> controller.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture2))); - setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); - waitForEnabledState(controller, booleanToEnabledState(true)); - } + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> mController.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture2))); + setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); + waitForEnabledState(mController, booleanToEnabledState(true)); } @Test public void setEnabled_noPermissions_throwsSecurityException() throws Exception { - for (ThreadNetworkController controller : getAllControllers()) { - CompletableFuture<Void> setFuture = new CompletableFuture<>(); - assertThrows( - SecurityException.class, - () -> controller.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture))); - } + CompletableFuture<Void> setFuture = new CompletableFuture<>(); + assertThrows( + SecurityException.class, + () -> mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture))); } @Test public void setEnabled_disable_leavesThreadNetwork() throws Exception { - for (ThreadNetworkController controller : getAllControllers()) { - joinRandomizedDatasetAndWait(controller); - - setEnabledAndWait(controller, false); - - runAsShell( - ACCESS_NETWORK_STATE, - () -> assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED)); - } + joinRandomizedDatasetAndWait(mController); + setEnabledAndWait(mController, false); + assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED); } @Test public void setEnabled_toggleAfterJoin_joinsThreadNetworkAgain() throws Exception { - for (ThreadNetworkController controller : getAllControllers()) { - joinRandomizedDatasetAndWait(controller); - - setEnabledAndWait(controller, false); - - runAsShell( - ACCESS_NETWORK_STATE, - () -> assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED)); + joinRandomizedDatasetAndWait(mController); - setEnabledAndWait(controller, true); + setEnabledAndWait(mController, false); + assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED); + setEnabledAndWait(mController, true); - runAsShell(ACCESS_NETWORK_STATE, () -> waitForAttachedState(controller)); - } + runAsShell(ACCESS_NETWORK_STATE, () -> waitForAttachedState(mController)); } @Test public void setEnabled_enableFollowedByDisable_allSucceed() throws Exception { - for (ThreadNetworkController controller : getAllControllers()) { - joinRandomizedDatasetAndWait(controller); - CompletableFuture<Void> setFuture1 = new CompletableFuture<>(); - CompletableFuture<Void> setFuture2 = new CompletableFuture<>(); - EnabledStateListener listener = new EnabledStateListener(controller); - listener.expectThreadEnabledState(STATE_ENABLED); + joinRandomizedDatasetAndWait(mController); + CompletableFuture<Void> setFuture1 = new CompletableFuture<>(); + CompletableFuture<Void> setFuture2 = new CompletableFuture<>(); + EnabledStateListener listener = new EnabledStateListener(mController); + listener.expectThreadEnabledState(STATE_ENABLED); - runAsShell( - PERMISSION_THREAD_NETWORK_PRIVILEGED, - () -> { - controller.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture1)); - controller.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture2)); - }); - - setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); - setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); - - listener.expectThreadEnabledState(STATE_DISABLING); - listener.expectThreadEnabledState(STATE_DISABLED); - - runAsShell( - ACCESS_NETWORK_STATE, - () -> assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED)); + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> { + mController.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture1)); + mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture2)); + }); + setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); + setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); - listener.unregisterStateCallback(); - } + listener.expectThreadEnabledState(STATE_DISABLING); + listener.expectThreadEnabledState(STATE_DISABLED); + assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED); + // FIXME: this is not called when a exception is thrown after the creation of `listener` + listener.unregisterStateCallback(); } + // TODO (b/322437869): add test case to verify when Thread is in DISABLING state, any commands // (join/leave/scheduleMigration/setEnabled) fail with ERROR_BUSY. This is not currently tested // because DISABLING has very short lifecycle, it's not possible to guarantee the command can be @@ -1054,9 +777,6 @@ public class ThreadNetworkControllerTest { @Test public void threadNetworkCallback_deviceAttached_threadNetworkIsAvailable() throws Exception { - ThreadNetworkController controller = mManager.getAllThreadNetworkControllers().get(0); - ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller); - CompletableFuture<Void> joinFuture = new CompletableFuture<>(); CompletableFuture<Network> networkFuture = new CompletableFuture<>(); ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); NetworkRequest networkRequest = @@ -1071,15 +791,223 @@ public class ThreadNetworkControllerTest { } }; - runAsShell( - PERMISSION_THREAD_NETWORK_PRIVILEGED, - () -> controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture))); + joinRandomizedDatasetAndWait(mController); runAsShell( ACCESS_NETWORK_STATE, () -> cm.registerNetworkCallback(networkRequest, networkCallback)); - joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); - runAsShell(ACCESS_NETWORK_STATE, () -> assertThat(isAttached(controller)).isTrue()); + assertThat(isAttached(mController)).isTrue(); assertThat(networkFuture.get(NETWORK_CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNotNull(); } + + private void grantPermissions(String... permissions) { + for (String permission : permissions) { + mGrantedPermissions.add(permission); + } + String[] allPermissions = new String[mGrantedPermissions.size()]; + mGrantedPermissions.toArray(allPermissions); + getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(allPermissions); + } + + private static void dropAllPermissions() { + getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); + } + + private static ActiveOperationalDataset newRandomizedDataset( + String networkName, ThreadNetworkController controller) throws Exception { + CompletableFuture<ActiveOperationalDataset> future = new CompletableFuture<>(); + controller.createRandomizedDataset(networkName, directExecutor(), future::complete); + return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); + } + + private static boolean isAttached(ThreadNetworkController controller) throws Exception { + return ThreadNetworkController.isAttached(getDeviceRole(controller)); + } + + private static int getDeviceRole(ThreadNetworkController controller) throws Exception { + CompletableFuture<Integer> future = new CompletableFuture<>(); + StateCallback callback = future::complete; + runAsShell( + ACCESS_NETWORK_STATE, + () -> controller.registerStateCallback(directExecutor(), callback)); + try { + return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); + } finally { + runAsShell(ACCESS_NETWORK_STATE, () -> controller.unregisterStateCallback(callback)); + } + } + + private static int waitForAttachedState(ThreadNetworkController controller) throws Exception { + List<Integer> attachedRoles = new ArrayList<>(); + attachedRoles.add(DEVICE_ROLE_CHILD); + attachedRoles.add(DEVICE_ROLE_ROUTER); + attachedRoles.add(DEVICE_ROLE_LEADER); + return waitForStateAnyOf(controller, attachedRoles); + } + + private static int waitForStateAnyOf( + ThreadNetworkController controller, List<Integer> deviceRoles) throws Exception { + CompletableFuture<Integer> future = new CompletableFuture<>(); + StateCallback callback = + newRole -> { + if (deviceRoles.contains(newRole)) { + future.complete(newRole); + } + }; + controller.registerStateCallback(directExecutor(), callback); + int role = future.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); + controller.unregisterStateCallback(callback); + return role; + } + + private static void waitForEnabledState(ThreadNetworkController controller, int state) + throws Exception { + CompletableFuture<Integer> future = new CompletableFuture<>(); + StateCallback callback = + new ThreadNetworkController.StateCallback() { + @Override + public void onDeviceRoleChanged(int r) {} + + @Override + public void onThreadEnableStateChanged(int enabled) { + if (enabled == state) { + future.complete(enabled); + } + } + }; + runAsShell( + ACCESS_NETWORK_STATE, + () -> controller.registerStateCallback(directExecutor(), callback)); + future.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); + runAsShell(ACCESS_NETWORK_STATE, () -> controller.unregisterStateCallback(callback)); + } + + private void leave( + ThreadNetworkController controller, + OutcomeReceiver<Void, ThreadNetworkException> receiver) { + runAsShell(THREAD_NETWORK_PRIVILEGED, () -> controller.leave(mExecutor, receiver)); + } + + private void scheduleMigration( + ThreadNetworkController controller, + PendingOperationalDataset pendingDataset, + OutcomeReceiver<Void, ThreadNetworkException> receiver) { + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> controller.scheduleMigration(pendingDataset, mExecutor, receiver)); + } + + private class EnabledStateListener { + private ArrayTrackRecord<Integer> mEnabledStates = new ArrayTrackRecord<>(); + private final ArrayTrackRecord<Integer>.ReadHead mReadHead = mEnabledStates.newReadHead(); + ThreadNetworkController mController; + StateCallback mCallback = + new ThreadNetworkController.StateCallback() { + @Override + public void onDeviceRoleChanged(int r) {} + + @Override + public void onThreadEnableStateChanged(int enabled) { + mEnabledStates.add(enabled); + } + }; + + EnabledStateListener(ThreadNetworkController controller) { + this.mController = controller; + runAsShell( + ACCESS_NETWORK_STATE, + () -> controller.registerStateCallback(mExecutor, mCallback)); + } + + public void expectThreadEnabledState(int enabled) { + assertNotNull(mReadHead.poll(ENABLED_TIMEOUT_MILLIS, e -> (e == enabled))); + } + + public void unregisterStateCallback() { + runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(mCallback)); + } + } + + private int booleanToEnabledState(boolean enabled) { + return enabled ? STATE_ENABLED : STATE_DISABLED; + } + + private void setEnabledAndWait(ThreadNetworkController controller, boolean enabled) + throws Exception { + CompletableFuture<Void> setFuture = new CompletableFuture<>(); + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> controller.setEnabled(enabled, mExecutor, newOutcomeReceiver(setFuture))); + setFuture.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); + waitForEnabledState(controller, booleanToEnabledState(enabled)); + } + + private CompletableFuture joinRandomizedDataset(ThreadNetworkController controller) + throws Exception { + ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller); + CompletableFuture<Void> joinFuture = new CompletableFuture<>(); + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture))); + return joinFuture; + } + + private void joinRandomizedDatasetAndWait(ThreadNetworkController controller) throws Exception { + CompletableFuture<Void> joinFuture = joinRandomizedDataset(controller); + joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS); + assertThat(isAttached(controller)).isTrue(); + } + + private static ActiveOperationalDataset getActiveOperationalDataset( + ThreadNetworkController controller) throws Exception { + CompletableFuture<ActiveOperationalDataset> future = new CompletableFuture<>(); + OperationalDatasetCallback callback = future::complete; + runAsShell( + ACCESS_NETWORK_STATE, + THREAD_NETWORK_PRIVILEGED, + () -> controller.registerOperationalDatasetCallback(directExecutor(), callback)); + try { + return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); + } finally { + runAsShell( + ACCESS_NETWORK_STATE, + THREAD_NETWORK_PRIVILEGED, + () -> controller.unregisterOperationalDatasetCallback(callback)); + } + } + + private static PendingOperationalDataset getPendingOperationalDataset( + ThreadNetworkController controller) throws Exception { + CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>(); + CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>(); + controller.registerOperationalDatasetCallback( + directExecutor(), newDatasetCallback(activeFuture, pendingFuture)); + return pendingFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS); + } + + private static OperationalDatasetCallback newDatasetCallback( + CompletableFuture<ActiveOperationalDataset> activeFuture, + CompletableFuture<PendingOperationalDataset> pendingFuture) { + return new OperationalDatasetCallback() { + @Override + public void onActiveOperationalDatasetChanged( + ActiveOperationalDataset activeOpDataset) { + activeFuture.complete(activeOpDataset); + } + + @Override + public void onPendingOperationalDatasetChanged( + PendingOperationalDataset pendingOpDataset) { + pendingFuture.complete(pendingOpDataset); + } + }; + } + + private static void assertDoesNotThrow(ThrowingRunnable runnable) { + try { + runnable.run(); + } catch (Throwable e) { + fail("Should not have thrown " + e); + } + } }