• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net.thread.cts;
18 
19 import static android.Manifest.permission.ACCESS_NETWORK_STATE;
20 import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
21 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
22 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
23 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
24 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
25 import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
26 import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_CHILD;
27 import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
28 import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_ROUTER;
29 import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED;
30 import static android.net.thread.ThreadNetworkController.EPHEMERAL_KEY_DISABLED;
31 import static android.net.thread.ThreadNetworkController.EPHEMERAL_KEY_ENABLED;
32 import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
33 import static android.net.thread.ThreadNetworkController.STATE_DISABLING;
34 import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
35 import static android.net.thread.ThreadNetworkController.THREAD_VERSION_1_3;
36 import static android.net.thread.ThreadNetworkException.ERROR_ABORTED;
37 import static android.net.thread.ThreadNetworkException.ERROR_BUSY;
38 import static android.net.thread.ThreadNetworkException.ERROR_FAILED_PRECONDITION;
39 import static android.net.thread.ThreadNetworkException.ERROR_REJECTED_BY_PEER;
40 import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
41 import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_FEATURE;
42 
43 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
44 
45 import static com.android.testutils.TestPermissionUtil.runAsShell;
46 
47 import static com.google.common.truth.Truth.assertThat;
48 import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
49 
50 import static org.junit.Assert.assertThrows;
51 import static org.junit.Assert.fail;
52 import static org.junit.Assume.assumeFalse;
53 import static org.junit.Assume.assumeTrue;
54 
55 import static java.util.concurrent.TimeUnit.MILLISECONDS;
56 
57 import android.content.Context;
58 import android.net.ConnectivityManager;
59 import android.net.Network;
60 import android.net.NetworkCapabilities;
61 import android.net.NetworkRequest;
62 import android.net.nsd.NsdManager;
63 import android.net.nsd.NsdServiceInfo;
64 import android.net.thread.ActiveOperationalDataset;
65 import android.net.thread.OperationalDatasetTimestamp;
66 import android.net.thread.PendingOperationalDataset;
67 import android.net.thread.ThreadConfiguration;
68 import android.net.thread.ThreadNetworkController;
69 import android.net.thread.ThreadNetworkController.OperationalDatasetCallback;
70 import android.net.thread.ThreadNetworkController.StateCallback;
71 import android.net.thread.ThreadNetworkException;
72 import android.net.thread.ThreadNetworkManager;
73 import android.net.thread.utils.TapTestNetworkTracker;
74 import android.net.thread.utils.ThreadFeatureCheckerRule;
75 import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
76 import android.os.Build;
77 import android.os.HandlerThread;
78 import android.os.OutcomeReceiver;
79 import android.platform.test.annotations.RequiresFlagsEnabled;
80 import android.platform.test.flag.junit.CheckFlagsRule;
81 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
82 import android.util.SparseIntArray;
83 
84 import androidx.annotation.NonNull;
85 import androidx.test.core.app.ApplicationProvider;
86 import androidx.test.filters.LargeTest;
87 
88 import com.android.net.module.util.ArrayTrackRecord;
89 import com.android.net.thread.flags.Flags;
90 import com.android.testutils.FunctionalUtils.ThrowingRunnable;
91 
92 import kotlin.Triple;
93 
94 import org.junit.After;
95 import org.junit.Before;
96 import org.junit.Ignore;
97 import org.junit.Rule;
98 import org.junit.Test;
99 import org.junit.runner.RunWith;
100 import org.junit.runners.Parameterized;
101 
102 import java.nio.charset.StandardCharsets;
103 import java.time.Duration;
104 import java.time.Instant;
105 import java.util.ArrayList;
106 import java.util.Arrays;
107 import java.util.Collection;
108 import java.util.HashSet;
109 import java.util.List;
110 import java.util.Map;
111 import java.util.Objects;
112 import java.util.Random;
113 import java.util.Set;
114 import java.util.concurrent.CompletableFuture;
115 import java.util.concurrent.ExecutionException;
116 import java.util.concurrent.Executor;
117 import java.util.concurrent.ExecutorService;
118 import java.util.concurrent.Executors;
119 import java.util.concurrent.TimeoutException;
120 import java.util.function.Consumer;
121 import java.util.function.Predicate;
122 
123 /** CTS tests for {@link ThreadNetworkController}. */
124 @LargeTest
125 @RequiresThreadFeature
126 @RunWith(Parameterized.class)
127 public class ThreadNetworkControllerTest {
128     private static final int JOIN_TIMEOUT_MILLIS = 30 * 1000;
129     private static final int LEAVE_TIMEOUT_MILLIS = 2_000;
130     private static final int MIGRATION_TIMEOUT_MILLIS = 40 * 1_000;
131     private static final int NETWORK_CALLBACK_TIMEOUT_MILLIS = 10 * 1000;
132     private static final int CALLBACK_TIMEOUT_MILLIS = 1_000;
133     private static final int ENABLED_TIMEOUT_MILLIS = 2_000;
134     private static final int SET_CONFIGURATION_TIMEOUT_MILLIS = 1_000;
135     private static final int SERVICE_DISCOVERY_TIMEOUT_MILLIS = 30_000;
136     private static final int SERVICE_LOST_TIMEOUT_MILLIS = 20_000;
137     private static final int VALID_POWER = 32_767;
138     private static final int VALID_CHANNEL = 20;
139     private static final int INVALID_CHANNEL = 10;
140     private static final String MESHCOP_SERVICE_TYPE = "_meshcop._udp";
141     private static final String THREAD_NETWORK_PRIVILEGED =
142             "android.permission.THREAD_NETWORK_PRIVILEGED";
143     private static final SparseIntArray CHANNEL_MAX_POWERS =
144             new SparseIntArray() {
145                 {
146                     put(VALID_CHANNEL, VALID_POWER);
147                 }
148             };
149     private static final Duration EPHEMERAL_KEY_LIFETIME = Duration.ofSeconds(1);
150 
151     @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
152 
153     @Rule
154     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
155 
156     private final Context mContext = ApplicationProvider.getApplicationContext();
157     private ExecutorService mExecutor;
158     private ThreadNetworkController mController;
159     private NsdManager mNsdManager;
160 
161     private Set<String> mGrantedPermissions;
162     private HandlerThread mHandlerThread;
163     private TapTestNetworkTracker mTestNetworkTracker;
164 
165     private final List<Consumer<ThreadConfiguration>> mConfigurationCallbacksToCleanUp =
166             new ArrayList<>();
167 
168     public final boolean mIsBorderRouterEnabled;
169     private final ThreadConfiguration mDefaultConfig;
170 
171     @Parameterized.Parameters
configArguments()172     public static Collection configArguments() {
173         return Arrays.asList(new Object[][] {{false}, {true}});
174     }
175 
ThreadNetworkControllerTest(boolean isBorderRouterEnabled)176     public ThreadNetworkControllerTest(boolean isBorderRouterEnabled) {
177         mIsBorderRouterEnabled = isBorderRouterEnabled;
178         mDefaultConfig =
179                 new ThreadConfiguration.Builder()
180                         .setBorderRouterEnabled(isBorderRouterEnabled)
181                         .build();
182     }
183 
184     @Before
setUp()185     public void setUp() throws Exception {
186         mController =
187                 mContext.getSystemService(ThreadNetworkManager.class)
188                         .getAllThreadNetworkControllers()
189                         .get(0);
190 
191         mGrantedPermissions = new HashSet<String>();
192         mExecutor = Executors.newSingleThreadExecutor();
193         mNsdManager = mContext.getSystemService(NsdManager.class);
194         mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
195         mHandlerThread.start();
196 
197         setEnabledAndWait(mController, true);
198         setConfigurationAndWait(mController, mDefaultConfig);
199         if (mDefaultConfig.isBorderRouterEnabled()) {
200             deactivateEphemeralKeyModeAndWait(mController);
201         }
202     }
203 
204     @After
tearDown()205     public void tearDown() throws Exception {
206         dropAllPermissions();
207         setEnabledAndWait(mController, true);
208         leaveAndWait(mController);
209         tearDownTestNetwork();
210         setConfigurationAndWait(mController, mDefaultConfig);
211         for (Consumer<ThreadConfiguration> configurationCallback :
212                 mConfigurationCallbacksToCleanUp) {
213             try {
214                 runAsShell(
215                         THREAD_NETWORK_PRIVILEGED,
216                         () -> mController.unregisterConfigurationCallback(configurationCallback));
217             } catch (IllegalArgumentException e) {
218                 // Ignore the exception when the callback is not registered.
219             }
220         }
221         mConfigurationCallbacksToCleanUp.clear();
222         if (mDefaultConfig.isBorderRouterEnabled()) {
223             deactivateEphemeralKeyModeAndWait(mController);
224         }
225     }
226 
227     @Test
getThreadVersion_returnsAtLeastThreadVersion1P3()228     public void getThreadVersion_returnsAtLeastThreadVersion1P3() {
229         assertThat(mController.getThreadVersion()).isAtLeast(THREAD_VERSION_1_3);
230     }
231 
232     @Test
registerStateCallback_permissionsGranted_returnsCurrentStates()233     public void registerStateCallback_permissionsGranted_returnsCurrentStates() throws Exception {
234         CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
235         StateCallback callback = deviceRole::complete;
236 
237         try {
238             runAsShell(
239                     ACCESS_NETWORK_STATE,
240                     () -> mController.registerStateCallback(mExecutor, callback));
241 
242             assertThat(deviceRole.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS))
243                     .isEqualTo(DEVICE_ROLE_STOPPED);
244         } finally {
245             runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
246         }
247     }
248 
249     @Test
subscribeThreadEnableState_getActiveDataset_onThreadEnableStateChangedNotCalled()250     public void subscribeThreadEnableState_getActiveDataset_onThreadEnableStateChangedNotCalled()
251             throws Exception {
252         EnabledStateListener listener = new EnabledStateListener(mController);
253         listener.expectThreadEnabledState(STATE_ENABLED);
254 
255         getActiveOperationalDataset(mController);
256 
257         listener.expectCallbackNotCalled();
258     }
259 
260     @Test
registerStateCallback_returnsUpdatedEnabledStates()261     public void registerStateCallback_returnsUpdatedEnabledStates() throws Exception {
262         CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
263         CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
264         EnabledStateListener listener = new EnabledStateListener(mController);
265 
266         try {
267             runAsShell(
268                     THREAD_NETWORK_PRIVILEGED,
269                     () -> {
270                         mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture1));
271                     });
272             setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
273 
274             runAsShell(
275                     THREAD_NETWORK_PRIVILEGED,
276                     () -> {
277                         mController.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture2));
278                     });
279             setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
280 
281             listener.expectThreadEnabledState(STATE_ENABLED);
282             listener.expectThreadEnabledState(STATE_DISABLING);
283             listener.expectThreadEnabledState(STATE_DISABLED);
284             listener.expectThreadEnabledState(STATE_ENABLED);
285         } finally {
286             listener.unregisterStateCallback();
287         }
288     }
289 
290     @Test
registerStateCallback_noPermissions_throwsSecurityException()291     public void registerStateCallback_noPermissions_throwsSecurityException() throws Exception {
292         dropAllPermissions();
293 
294         assertThrows(
295                 SecurityException.class,
296                 () -> mController.registerStateCallback(mExecutor, role -> {}));
297     }
298 
299     @Test
registerStateCallback_alreadyRegistered_throwsIllegalArgumentException()300     public void registerStateCallback_alreadyRegistered_throwsIllegalArgumentException()
301             throws Exception {
302         grantPermissions(ACCESS_NETWORK_STATE);
303         CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
304         StateCallback callback = role -> deviceRole.complete(role);
305 
306         mController.registerStateCallback(mExecutor, callback);
307 
308         assertThrows(
309                 IllegalArgumentException.class,
310                 () -> mController.registerStateCallback(mExecutor, callback));
311     }
312 
313     @Test
unregisterStateCallback_noPermissions_throwsSecurityException()314     public void unregisterStateCallback_noPermissions_throwsSecurityException() throws Exception {
315         CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
316         StateCallback callback = role -> deviceRole.complete(role);
317         runAsShell(
318                 ACCESS_NETWORK_STATE, () -> mController.registerStateCallback(mExecutor, callback));
319 
320         try {
321             dropAllPermissions();
322             assertThrows(
323                     SecurityException.class, () -> mController.unregisterStateCallback(callback));
324         } finally {
325             runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
326         }
327     }
328 
329     @Test
unregisterStateCallback_callbackRegistered_success()330     public void unregisterStateCallback_callbackRegistered_success() throws Exception {
331         grantPermissions(ACCESS_NETWORK_STATE);
332         CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
333         StateCallback callback = role -> deviceRole.complete(role);
334 
335         assertDoesNotThrow(() -> mController.registerStateCallback(mExecutor, callback));
336         mController.unregisterStateCallback(callback);
337     }
338 
339     @Test
unregisterStateCallback_callbackNotRegistered_throwsIllegalArgumentException()340     public void unregisterStateCallback_callbackNotRegistered_throwsIllegalArgumentException()
341             throws Exception {
342         CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
343         StateCallback callback = role -> deviceRole.complete(role);
344 
345         assertThrows(
346                 IllegalArgumentException.class,
347                 () -> mController.unregisterStateCallback(callback));
348     }
349 
350     @Test
unregisterStateCallback_alreadyUnregistered_throwsIllegalArgumentException()351     public void unregisterStateCallback_alreadyUnregistered_throwsIllegalArgumentException()
352             throws Exception {
353         grantPermissions(ACCESS_NETWORK_STATE);
354         CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
355         StateCallback callback = deviceRole::complete;
356         mController.registerStateCallback(mExecutor, callback);
357         mController.unregisterStateCallback(callback);
358 
359         assertThrows(
360                 IllegalArgumentException.class,
361                 () -> mController.unregisterStateCallback(callback));
362     }
363 
364     @Test
registerOperationalDatasetCallback_permissionsGranted_returnsCurrentStates()365     public void registerOperationalDatasetCallback_permissionsGranted_returnsCurrentStates()
366             throws Exception {
367         grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
368         CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
369         CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
370         var callback = newDatasetCallback(activeFuture, pendingFuture);
371 
372         try {
373             mController.registerOperationalDatasetCallback(mExecutor, callback);
374 
375             assertThat(activeFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
376             assertThat(pendingFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
377         } finally {
378             mController.unregisterOperationalDatasetCallback(callback);
379         }
380     }
381 
382     @Test
registerOperationalDatasetCallback_noPermissions_throwsSecurityException()383     public void registerOperationalDatasetCallback_noPermissions_throwsSecurityException()
384             throws Exception {
385         dropAllPermissions();
386         CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
387         CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
388         var callback = newDatasetCallback(activeFuture, pendingFuture);
389 
390         assertThrows(
391                 SecurityException.class,
392                 () -> mController.registerOperationalDatasetCallback(mExecutor, callback));
393     }
394 
395     @Test
unregisterOperationalDatasetCallback_callbackRegistered_success()396     public void unregisterOperationalDatasetCallback_callbackRegistered_success() throws Exception {
397         grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
398         CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
399         CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
400         var callback = newDatasetCallback(activeFuture, pendingFuture);
401         mController.registerOperationalDatasetCallback(mExecutor, callback);
402 
403         assertDoesNotThrow(() -> mController.unregisterOperationalDatasetCallback(callback));
404     }
405 
406     @Test
unregisterOperationalDatasetCallback_noPermissions_throwsSecurityException()407     public void unregisterOperationalDatasetCallback_noPermissions_throwsSecurityException()
408             throws Exception {
409         CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
410         CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
411         var callback = newDatasetCallback(activeFuture, pendingFuture);
412         runAsShell(
413                 ACCESS_NETWORK_STATE,
414                 THREAD_NETWORK_PRIVILEGED,
415                 () -> mController.registerOperationalDatasetCallback(mExecutor, callback));
416 
417         try {
418             dropAllPermissions();
419             assertThrows(
420                     SecurityException.class,
421                     () -> mController.unregisterOperationalDatasetCallback(callback));
422         } finally {
423             runAsShell(
424                     ACCESS_NETWORK_STATE,
425                     THREAD_NETWORK_PRIVILEGED,
426                     () -> mController.unregisterOperationalDatasetCallback(callback));
427         }
428     }
429 
newOutcomeReceiver( CompletableFuture<V> future)430     private static <V> OutcomeReceiver<V, ThreadNetworkException> newOutcomeReceiver(
431             CompletableFuture<V> future) {
432         return new OutcomeReceiver<V, ThreadNetworkException>() {
433             @Override
434             public void onResult(V result) {
435                 future.complete(result);
436             }
437 
438             @Override
439             public void onError(ThreadNetworkException e) {
440                 future.completeExceptionally(e);
441             }
442         };
443     }
444 
445     @Test
446     public void join_withPrivilegedPermission_success() throws Exception {
447         ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
448         CompletableFuture<Void> joinFuture = new CompletableFuture<>();
449 
450         runAsShell(
451                 THREAD_NETWORK_PRIVILEGED,
452                 () -> mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
453         joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
454 
455         assertThat(isAttached(mController)).isTrue();
456         assertThat(getActiveOperationalDataset(mController)).isEqualTo(activeDataset);
457     }
458 
459     @Test
460     public void join_withoutPrivilegedPermission_throwsSecurityException() throws Exception {
461         dropAllPermissions();
462         ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
463 
464         assertThrows(
465                 SecurityException.class, () -> mController.join(activeDataset, mExecutor, v -> {}));
466     }
467 
468     @Test
469     public void join_threadDisabled_failsWithErrorThreadDisabled() throws Exception {
470         setEnabledAndWait(mController, false);
471         ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
472         CompletableFuture<Void> joinFuture = new CompletableFuture<>();
473 
474         runAsShell(
475                 THREAD_NETWORK_PRIVILEGED,
476                 () -> mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
477 
478         var thrown =
479                 assertThrows(
480                         ExecutionException.class,
481                         () -> joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS));
482         var threadException = (ThreadNetworkException) thrown.getCause();
483         assertThat(threadException.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED);
484     }
485 
486     @Test
487     public void join_concurrentRequests_firstOneIsAborted() throws Exception {
488         final byte[] KEY_1 = new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
489         final byte[] KEY_2 = new byte[] {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
490         ActiveOperationalDataset activeDataset1 =
491                 new ActiveOperationalDataset.Builder(newRandomizedDataset("TestNet", mController))
492                         .setNetworkKey(KEY_1)
493                         .build();
494         ActiveOperationalDataset activeDataset2 =
495                 new ActiveOperationalDataset.Builder(activeDataset1).setNetworkKey(KEY_2).build();
496         CompletableFuture<Void> joinFuture1 = new CompletableFuture<>();
497         CompletableFuture<Void> joinFuture2 = new CompletableFuture<>();
498 
499         runAsShell(
500                 THREAD_NETWORK_PRIVILEGED,
501                 () -> {
502                     mController.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture1));
503                     mController.join(activeDataset2, mExecutor, newOutcomeReceiver(joinFuture2));
504                 });
505 
506         var thrown =
507                 assertThrows(
508                         ExecutionException.class,
509                         () -> joinFuture1.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS));
510         var threadException = (ThreadNetworkException) thrown.getCause();
511         assertThat(threadException.getErrorCode()).isEqualTo(ERROR_ABORTED);
512         joinFuture2.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
513         assertThat(isAttached(mController)).isTrue();
514         assertThat(getActiveOperationalDataset(mController)).isEqualTo(activeDataset2);
515     }
516 
517     @Test
518     public void leave_withPrivilegedPermission_success() throws Exception {
519         CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
520         joinRandomizedDatasetAndWait(mController);
521 
522         runAsShell(
523                 THREAD_NETWORK_PRIVILEGED,
524                 () -> mController.leave(mExecutor, newOutcomeReceiver(leaveFuture)));
525         leaveFuture.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
526 
527         assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
528     }
529 
530     @Test
531     public void leave_withoutPrivilegedPermission_throwsSecurityException() {
532         dropAllPermissions();
533 
534         assertThrows(SecurityException.class, () -> mController.leave(mExecutor, v -> {}));
535     }
536 
537     @Test
538     public void leave_threadDisabled_success() throws Exception {
539         setEnabledAndWait(mController, false);
540         CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
541 
542         leave(mController, newOutcomeReceiver(leaveFuture));
543         leaveFuture.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
544 
545         assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
546     }
547 
548     @Test
549     public void leave_concurrentRequests_bothSuccess() throws Exception {
550         CompletableFuture<Void> leaveFuture1 = new CompletableFuture<>();
551         CompletableFuture<Void> leaveFuture2 = new CompletableFuture<>();
552         joinRandomizedDatasetAndWait(mController);
553 
554         runAsShell(
555                 THREAD_NETWORK_PRIVILEGED,
556                 () -> {
557                     mController.leave(mExecutor, newOutcomeReceiver(leaveFuture1));
558                     mController.leave(mExecutor, newOutcomeReceiver(leaveFuture2));
559                 });
560 
561         leaveFuture1.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
562         leaveFuture2.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
563         assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
564     }
565 
566     @Test
567     public void scheduleMigration_withPrivilegedPermission_newDatasetApplied() throws Exception {
568         grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
569         ActiveOperationalDataset activeDataset1 =
570                 new ActiveOperationalDataset.Builder(newRandomizedDataset("TestNet", mController))
571                         .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
572                         .setExtendedPanId(new byte[] {1, 1, 1, 1, 1, 1, 1, 1})
573                         .build();
574         ActiveOperationalDataset activeDataset2 =
575                 new ActiveOperationalDataset.Builder(activeDataset1)
576                         .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
577                         .setNetworkName("ThreadNet2")
578                         .build();
579         PendingOperationalDataset pendingDataset2 =
580                 new PendingOperationalDataset(
581                         activeDataset2,
582                         OperationalDatasetTimestamp.fromInstant(Instant.now()),
583                         Duration.ofSeconds(30));
584         CompletableFuture<Void> joinFuture = new CompletableFuture<>();
585         CompletableFuture<Void> migrateFuture = new CompletableFuture<>();
586         mController.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture));
587         joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
588 
589         mController.scheduleMigration(
590                 pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture));
591         migrateFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
592 
593         CompletableFuture<Boolean> dataset2IsApplied = new CompletableFuture<>();
594         CompletableFuture<Boolean> pendingDatasetIsRemoved = new CompletableFuture<>();
595         OperationalDatasetCallback datasetCallback =
596                 new OperationalDatasetCallback() {
597                     @Override
598                     public void onActiveOperationalDatasetChanged(
599                             ActiveOperationalDataset activeDataset) {
600                         if (Objects.equals(activeDataset, activeDataset2)) {
601                             dataset2IsApplied.complete(true);
602                         }
603                     }
604 
605                     @Override
606                     public void onPendingOperationalDatasetChanged(
607                             PendingOperationalDataset pendingDataset) {
608                         if (pendingDataset == null) {
609                             pendingDatasetIsRemoved.complete(true);
610                         }
611                     }
612                 };
613         mController.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
614         try {
615             assertThat(dataset2IsApplied.get(MIGRATION_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
616             assertThat(pendingDatasetIsRemoved.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
617         } finally {
618             mController.unregisterOperationalDatasetCallback(datasetCallback);
619         }
620     }
621 
622     @Test
623     public void scheduleMigration_whenNotAttached_failWithPreconditionError() throws Exception {
624         grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
625         PendingOperationalDataset pendingDataset =
626                 new PendingOperationalDataset(
627                         newRandomizedDataset("TestNet", mController),
628                         OperationalDatasetTimestamp.fromInstant(Instant.now()),
629                         Duration.ofSeconds(30));
630         CompletableFuture<Void> migrateFuture = new CompletableFuture<>();
631 
632         mController.scheduleMigration(pendingDataset, mExecutor, newOutcomeReceiver(migrateFuture));
633 
634         ThreadNetworkException thrown =
635                 (ThreadNetworkException)
636                         assertThrows(ExecutionException.class, migrateFuture::get).getCause();
637         assertThat(thrown.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
638     }
639 
640     @Test
641     public void scheduleMigration_secondRequestHasSmallerTimestamp_rejectedByLeader()
642             throws Exception {
643         grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
644         final ActiveOperationalDataset activeDataset =
645                 new ActiveOperationalDataset.Builder(newRandomizedDataset("testNet", mController))
646                         .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
647                         .build();
648         ActiveOperationalDataset activeDataset1 =
649                 new ActiveOperationalDataset.Builder(activeDataset)
650                         .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
651                         .setNetworkName("testNet1")
652                         .build();
653         PendingOperationalDataset pendingDataset1 =
654                 new PendingOperationalDataset(
655                         activeDataset1,
656                         new OperationalDatasetTimestamp(100, 0, false),
657                         Duration.ofSeconds(30));
658         ActiveOperationalDataset activeDataset2 =
659                 new ActiveOperationalDataset.Builder(activeDataset)
660                         .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false))
661                         .setNetworkName("testNet2")
662                         .build();
663         PendingOperationalDataset pendingDataset2 =
664                 new PendingOperationalDataset(
665                         activeDataset2,
666                         new OperationalDatasetTimestamp(20, 0, false),
667                         Duration.ofSeconds(30));
668         CompletableFuture<Void> joinFuture = new CompletableFuture<>();
669         CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>();
670         CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>();
671         mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
672         joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
673 
674         mController.scheduleMigration(
675                 pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
676         migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
677         mController.scheduleMigration(
678                 pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
679 
680         ThreadNetworkException thrown =
681                 (ThreadNetworkException)
682                         assertThrows(ExecutionException.class, migrateFuture2::get).getCause();
683         assertThat(thrown.getErrorCode()).isEqualTo(ERROR_REJECTED_BY_PEER);
684     }
685 
686     @Test
687     public void scheduleMigration_secondRequestHasLargerTimestamp_newDatasetApplied()
688             throws Exception {
689         grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
690         final ActiveOperationalDataset activeDataset =
691                 new ActiveOperationalDataset.Builder(newRandomizedDataset("validName", mController))
692                         .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
693                         .build();
694         ActiveOperationalDataset activeDataset1 =
695                 new ActiveOperationalDataset.Builder(activeDataset)
696                         .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
697                         .setNetworkName("testNet1")
698                         .build();
699         PendingOperationalDataset pendingDataset1 =
700                 new PendingOperationalDataset(
701                         activeDataset1,
702                         new OperationalDatasetTimestamp(100, 0, false),
703                         Duration.ofSeconds(30));
704         ActiveOperationalDataset activeDataset2 =
705                 new ActiveOperationalDataset.Builder(activeDataset)
706                         .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false))
707                         .setNetworkName("testNet2")
708                         .build();
709         PendingOperationalDataset pendingDataset2 =
710                 new PendingOperationalDataset(
711                         activeDataset2,
712                         new OperationalDatasetTimestamp(200, 0, false),
713                         Duration.ofSeconds(30));
714         CompletableFuture<Void> joinFuture = new CompletableFuture<>();
715         CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>();
716         CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>();
717         mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
718         joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
719 
720         mController.scheduleMigration(
721                 pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
722         migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
723         mController.scheduleMigration(
724                 pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
725         migrateFuture2.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
726 
727         CompletableFuture<Boolean> dataset2IsApplied = new CompletableFuture<>();
728         CompletableFuture<Boolean> pendingDatasetIsRemoved = new CompletableFuture<>();
729         OperationalDatasetCallback datasetCallback =
730                 new OperationalDatasetCallback() {
731                     @Override
732                     public void onActiveOperationalDatasetChanged(
733                             ActiveOperationalDataset activeDataset) {
734                         if (activeDataset.equals(activeDataset2)) {
735                             dataset2IsApplied.complete(true);
736                         }
737                     }
738 
739                     @Override
740                     public void onPendingOperationalDatasetChanged(
741                             PendingOperationalDataset pendingDataset) {
742                         if (pendingDataset == null) {
743                             pendingDatasetIsRemoved.complete(true);
744                         }
745                     }
746                 };
747         mController.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
748         try {
749             assertThat(dataset2IsApplied.get(MIGRATION_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
750             assertThat(pendingDatasetIsRemoved.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
751         } finally {
752             mController.unregisterOperationalDatasetCallback(datasetCallback);
753         }
754     }
755 
756     @Test
757     public void scheduleMigration_threadDisabled_failsWithErrorThreadDisabled() throws Exception {
758         ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
759         PendingOperationalDataset pendingDataset =
760                 new PendingOperationalDataset(
761                         activeDataset,
762                         OperationalDatasetTimestamp.fromInstant(Instant.now()),
763                         Duration.ofSeconds(30));
764         joinRandomizedDatasetAndWait(mController);
765         CompletableFuture<Void> migrationFuture = new CompletableFuture<>();
766 
767         setEnabledAndWait(mController, false);
768 
769         scheduleMigration(mController, pendingDataset, newOutcomeReceiver(migrationFuture));
770 
771         ThreadNetworkException thrown =
772                 (ThreadNetworkException)
773                         assertThrows(ExecutionException.class, migrationFuture::get).getCause();
774         assertThat(thrown.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED);
775     }
776 
777     @Test
778     public void createRandomizedDataset_wrongNetworkNameLength_throwsIllegalArgumentException() {
779         assertThrows(
780                 IllegalArgumentException.class,
781                 () -> mController.createRandomizedDataset("", mExecutor, dataset -> {}));
782 
783         assertThrows(
784                 IllegalArgumentException.class,
785                 () ->
786                         mController.createRandomizedDataset(
787                                 "ANetNameIs17Bytes", mExecutor, dataset -> {}));
788     }
789 
790     @Test
791     public void createRandomizedDataset_validNetworkName_success() throws Exception {
792         ActiveOperationalDataset dataset = newRandomizedDataset("validName", mController);
793 
794         assertThat(dataset.getNetworkName()).isEqualTo("validName");
795         assertThat(dataset.getPanId()).isLessThan(0xffff);
796         assertThat(dataset.getChannelMask().size()).isAtLeast(1);
797         assertThat(dataset.getExtendedPanId()).hasLength(8);
798         assertThat(dataset.getNetworkKey()).hasLength(16);
799         assertThat(dataset.getPskc()).hasLength(16);
800         assertThat(dataset.getMeshLocalPrefix().getPrefixLength()).isEqualTo(64);
801         assertThat(dataset.getMeshLocalPrefix().getRawAddress()[0]).isEqualTo((byte) 0xfd);
802     }
803 
804     @Test
805     public void setEnabled_permissionsGranted_succeeds() throws Exception {
806         CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
807         CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
808 
809         runAsShell(
810                 THREAD_NETWORK_PRIVILEGED,
811                 () -> mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture1)));
812         setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
813         waitForEnabledState(mController, booleanToEnabledState(false));
814 
815         runAsShell(
816                 THREAD_NETWORK_PRIVILEGED,
817                 () -> mController.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture2)));
818         setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
819         waitForEnabledState(mController, booleanToEnabledState(true));
820     }
821 
822     @Test
823     public void setEnabled_noPermissions_throwsSecurityException() throws Exception {
824         CompletableFuture<Void> setFuture = new CompletableFuture<>();
825         assertThrows(
826                 SecurityException.class,
827                 () -> mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture)));
828     }
829 
830     @Test
831     public void setEnabled_disable_leavesThreadNetwork() throws Exception {
832         joinRandomizedDatasetAndWait(mController);
833         setEnabledAndWait(mController, false);
834         assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
835     }
836 
837     @Test
838     public void setEnabled_enableFollowedByDisable_allSucceed() throws Exception {
839         joinRandomizedDatasetAndWait(mController);
840         CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
841         CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
842         EnabledStateListener listener = new EnabledStateListener(mController);
843         listener.expectThreadEnabledState(STATE_ENABLED);
844 
845         runAsShell(
846                 THREAD_NETWORK_PRIVILEGED,
847                 () -> {
848                     mController.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture1));
849                     mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture2));
850                 });
851         setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
852         setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
853 
854         listener.expectThreadEnabledState(STATE_DISABLING);
855         listener.expectThreadEnabledState(STATE_DISABLED);
856         assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
857         // FIXME: this is not called when a exception is thrown after the creation of `listener`
858         listener.unregisterStateCallback();
859     }
860 
861     @Test
862     @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
863     public void getMaxEphemeralKeyLifetime_isLargerThanZero() {
864         assertThat(mController.getMaxEphemeralKeyLifetime()).isGreaterThan(Duration.ZERO);
865     }
866 
867     @Test
868     @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
869     public void activateEphemeralKeyMode_withPrivilegedPermission_succeeds() throws Exception {
870         assumeTrue(mDefaultConfig.isBorderRouterEnabled());
871         joinRandomizedDatasetAndWait(mController);
872         CompletableFuture<Void> startFuture = new CompletableFuture<>();
873 
874         runAsShell(
875                 THREAD_NETWORK_PRIVILEGED,
876                 () ->
877                         mController.activateEphemeralKeyMode(
878                                 EPHEMERAL_KEY_LIFETIME,
879                                 mExecutor,
880                                 newOutcomeReceiver(startFuture)));
881 
882         startFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
883     }
884 
885     @Test
886     @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
887     public void activateEphemeralKeyMode_withoutPrivilegedPermission_throwsSecurityException()
888             throws Exception {
889         assumeTrue(mDefaultConfig.isBorderRouterEnabled());
890         dropAllPermissions();
891 
892         assertThrows(
893                 SecurityException.class,
894                 () ->
895                         mController.activateEphemeralKeyMode(
896                                 EPHEMERAL_KEY_LIFETIME, mExecutor, v -> {}));
897     }
898 
899     @Test
900     @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
901     public void activateEphemeralKeyMode_withZeroLifetime_throwsIllegalArgumentException()
902             throws Exception {
903         assumeTrue(mDefaultConfig.isBorderRouterEnabled());
904         grantPermissions(THREAD_NETWORK_PRIVILEGED);
905 
906         assertThrows(
907                 IllegalArgumentException.class,
908                 () -> mController.activateEphemeralKeyMode(Duration.ZERO, mExecutor, v -> {}));
909     }
910 
911     @Test
912     @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
913     public void activateEphemeralKeyMode_withInvalidLargeLifetime_throwsIllegalArgumentException()
914             throws Exception {
915         assumeTrue(mDefaultConfig.isBorderRouterEnabled());
916         grantPermissions(THREAD_NETWORK_PRIVILEGED);
917         Duration lifetime = mController.getMaxEphemeralKeyLifetime().plusMillis(1);
918 
919         assertThrows(
920                 IllegalArgumentException.class,
921                 () -> mController.activateEphemeralKeyMode(lifetime, Runnable::run, v -> {}));
922     }
923 
924     @Test
925     @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
926     public void activateEphemeralKeyMode_concurrentRequests_secondOneFailsWithBusyError()
927             throws Exception {
928         assumeTrue(mDefaultConfig.isBorderRouterEnabled());
929         joinRandomizedDatasetAndWait(mController);
930         CompletableFuture<Void> future1 = new CompletableFuture<>();
931         CompletableFuture<Void> future2 = new CompletableFuture<>();
932 
933         runAsShell(
934                 THREAD_NETWORK_PRIVILEGED,
935                 () -> {
936                     mController.activateEphemeralKeyMode(
937                             EPHEMERAL_KEY_LIFETIME, mExecutor, newOutcomeReceiver(future1));
938                     mController.activateEphemeralKeyMode(
939                             EPHEMERAL_KEY_LIFETIME, mExecutor, newOutcomeReceiver(future2));
940                 });
941 
942         var thrown =
943                 assertThrows(
944                         ExecutionException.class,
945                         () -> {
946                             future2.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
947                         });
948         var threadException = (ThreadNetworkException) thrown.getCause();
949         assertThat(threadException.getErrorCode()).isEqualTo(ERROR_BUSY);
950     }
951 
952     @Test
953     @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
954     public void activateEphemeralKeyMode_notBorderRouter_failsWithFailedPrecondition()
955             throws Exception {
956         setConfigurationAndWait(
957                 mController,
958                 new ThreadConfiguration.Builder().setBorderRouterEnabled(false).build());
959         grantPermissions(THREAD_NETWORK_PRIVILEGED);
960         CompletableFuture<Void> future = new CompletableFuture<>();
961 
962         mController.activateEphemeralKeyMode(
963                 Duration.ofSeconds(1), mExecutor, newOutcomeReceiver(future));
964 
965         var thrown =
966                 assertThrows(
967                         ExecutionException.class,
968                         () -> future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS));
969         var threadException = (ThreadNetworkException) thrown.getCause();
970         assertThat(threadException.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
971     }
972 
973     @Test
974     @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
975     public void deactivateEphemeralKeyMode_withoutPrivilegedPermission_throwsSecurityException()
976             throws Exception {
977         assumeTrue(mDefaultConfig.isBorderRouterEnabled());
978         dropAllPermissions();
979 
980         assertThrows(
981                 SecurityException.class,
982                 () -> mController.deactivateEphemeralKeyMode(mExecutor, v -> {}));
983     }
984 
985     @Test
986     @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
987     public void deactivateEphemeralKeyMode_notBorderRouter_failsWithFailedPrecondition()
988             throws Exception {
989         assumeFalse(mDefaultConfig.isBorderRouterEnabled());
990         grantPermissions(THREAD_NETWORK_PRIVILEGED);
991         CompletableFuture<Void> future = new CompletableFuture<>();
992 
993         mController.deactivateEphemeralKeyMode(mExecutor, newOutcomeReceiver(future));
994 
995         var thrown =
996                 assertThrows(
997                         ExecutionException.class,
998                         () -> future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS));
999         var threadException = (ThreadNetworkException) thrown.getCause();
1000         assertThat(threadException.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
1001     }
1002 
1003     @Test
1004     @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
1005     public void subscribeEpskcState_permissionsGranted_returnsCurrentState() throws Exception {
1006         assumeTrue(mDefaultConfig.isBorderRouterEnabled());
1007         CompletableFuture<Integer> stateFuture = new CompletableFuture<>();
1008         CompletableFuture<String> ephemeralKeyFuture = new CompletableFuture<>();
1009         CompletableFuture<Instant> expiryFuture = new CompletableFuture<>();
1010         StateCallback callback =
1011                 new ThreadNetworkController.StateCallback() {
1012                     @Override
1013                     public void onDeviceRoleChanged(int r) {}
1014 
1015                     @Override
1016                     public void onEphemeralKeyStateChanged(
1017                             int state, String ephemeralKey, Instant expiry) {
1018                         stateFuture.complete(state);
1019                         ephemeralKeyFuture.complete(ephemeralKey);
1020                         expiryFuture.complete(expiry);
1021                     }
1022                 };
1023 
1024         runAsShell(
1025                 ACCESS_NETWORK_STATE,
1026                 THREAD_NETWORK_PRIVILEGED,
1027                 () -> mController.registerStateCallback(mExecutor, callback));
1028 
1029         try {
1030             assertThat(stateFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS))
1031                     .isEqualTo(EPHEMERAL_KEY_DISABLED);
1032             assertThat(ephemeralKeyFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
1033             assertThat(expiryFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
1034         } finally {
1035             runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
1036         }
1037     }
1038 
1039     @Test
1040     @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
1041     public void subscribeEpskcState_withoutThreadPriviledgedPermission_returnsNullEphemeralKey()
1042             throws Exception {
1043         assumeTrue(mDefaultConfig.isBorderRouterEnabled());
1044         CompletableFuture<Integer> stateFuture = new CompletableFuture<>();
1045         CompletableFuture<String> ephemeralKeyFuture = new CompletableFuture<>();
1046         CompletableFuture<Instant> expiryFuture = new CompletableFuture<>();
1047         StateCallback callback =
1048                 new ThreadNetworkController.StateCallback() {
1049                     @Override
1050                     public void onDeviceRoleChanged(int r) {}
1051 
1052                     @Override
1053                     public void onEphemeralKeyStateChanged(
1054                             int state, String ephemeralKey, Instant expiry) {
1055                         stateFuture.complete(state);
1056                         ephemeralKeyFuture.complete(ephemeralKey);
1057                         expiryFuture.complete(expiry);
1058                     }
1059                 };
1060         joinRandomizedDatasetAndWait(mController);
1061         activateEphemeralKeyModeAndWait(mController);
1062 
1063         runAsShell(
1064                 ACCESS_NETWORK_STATE, () -> mController.registerStateCallback(mExecutor, callback));
1065 
1066         try {
1067             assertThat(stateFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS))
1068                     .isEqualTo(EPHEMERAL_KEY_ENABLED);
1069             assertThat(ephemeralKeyFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
1070             assertThat(
1071                             expiryFuture
1072                                     .get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)
1073                                     .isAfter(Instant.now()))
1074                     .isTrue();
1075         } finally {
1076             runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
1077         }
1078     }
1079 
1080     @Test
1081     @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
1082     public void subscribeEpskcState_ephemralKeyStateChanged_returnsUpdatedState() throws Exception {
1083         assumeTrue(mDefaultConfig.isBorderRouterEnabled());
1084         EphemeralKeyStateListener listener = new EphemeralKeyStateListener(mController);
1085         joinRandomizedDatasetAndWait(mController);
1086 
1087         try {
1088             activateEphemeralKeyModeAndWait(mController);
1089             deactivateEphemeralKeyModeAndWait(mController);
1090 
1091             listener.expectThreadEphemeralKeyMode(EPHEMERAL_KEY_DISABLED);
1092             listener.expectThreadEphemeralKeyMode(EPHEMERAL_KEY_ENABLED);
1093             listener.expectThreadEphemeralKeyMode(EPHEMERAL_KEY_DISABLED);
1094         } finally {
1095             listener.unregisterStateCallback();
1096         }
1097     }
1098 
1099     @Test
1100     @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
1101     public void subscribeEpskcState_epskcEnabled_returnsSameExpiry() throws Exception {
1102         assumeTrue(mDefaultConfig.isBorderRouterEnabled());
1103         EphemeralKeyStateListener listener1 = new EphemeralKeyStateListener(mController);
1104         Triple<Integer, String, Instant> epskc1;
1105         try {
1106             joinRandomizedDatasetAndWait(mController);
1107             activateEphemeralKeyModeAndWait(mController);
1108             epskc1 = listener1.expectThreadEphemeralKeyMode(EPHEMERAL_KEY_ENABLED);
1109         } finally {
1110             listener1.unregisterStateCallback();
1111         }
1112 
1113         EphemeralKeyStateListener listener2 = new EphemeralKeyStateListener(mController);
1114         try {
1115             Triple<Integer, String, Instant> epskc2 =
1116                     listener2.expectThreadEphemeralKeyMode(EPHEMERAL_KEY_ENABLED);
1117 
1118             assertThat(epskc2.getSecond()).isEqualTo(epskc1.getSecond());
1119             // allow time precision loss of a second since the value is passed via IPC
1120             assertThat(epskc2.getThird()).isGreaterThan(epskc1.getThird().minusSeconds(1));
1121             assertThat(epskc2.getThird()).isLessThan(epskc1.getThird().plusSeconds(1));
1122         } finally {
1123             listener2.unregisterStateCallback();
1124         }
1125     }
1126 
1127     // TODO (b/322437869): add test case to verify when Thread is in DISABLING state, any commands
1128     // (join/leave/scheduleMigration/setEnabled) fail with ERROR_BUSY. This is not currently tested
1129     // because DISABLING has very short lifecycle, it's not possible to guarantee the command can be
1130     // sent before state changes to DISABLED.
1131 
1132     @Test
1133     public void threadNetworkCallback_deviceAttached_threadNetworkIsAvailable() throws Exception {
1134         CompletableFuture<Network> networkFuture = new CompletableFuture<>();
1135         ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
1136         NetworkRequest.Builder networkRequestBuilder =
1137                 new NetworkRequest.Builder().addTransportType(NetworkCapabilities.TRANSPORT_THREAD);
1138         // Before V, we need to explicitly set `NET_CAPABILITY_LOCAL_NETWORK` capability to request
1139         // a Thread network.
1140         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
1141             networkRequestBuilder.addCapability(NET_CAPABILITY_LOCAL_NETWORK);
1142         }
1143         NetworkRequest networkRequest = networkRequestBuilder.build();
1144         ConnectivityManager.NetworkCallback networkCallback =
1145                 new ConnectivityManager.NetworkCallback() {
1146                     @Override
1147                     public void onAvailable(Network network) {
1148                         networkFuture.complete(network);
1149                     }
1150                 };
1151 
1152         joinRandomizedDatasetAndWait(mController);
1153         runAsShell(
1154                 ACCESS_NETWORK_STATE,
1155                 () -> cm.registerNetworkCallback(networkRequest, networkCallback));
1156 
1157         assertThat(isAttached(mController)).isTrue();
1158         assertThat(networkFuture.get(NETWORK_CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNotNull();
1159         NetworkCapabilities caps =
1160                 runAsShell(
1161                         ACCESS_NETWORK_STATE, () -> cm.getNetworkCapabilities(networkFuture.get()));
1162         assertThat(caps).isNotNull();
1163         assertThat(caps.hasTransport(NetworkCapabilities.TRANSPORT_THREAD)).isTrue();
1164         assertThat(caps.getCapabilities())
1165                 .asList()
1166                 .containsAtLeast(
1167                         NET_CAPABILITY_LOCAL_NETWORK,
1168                         NET_CAPABILITY_NOT_METERED,
1169                         NET_CAPABILITY_NOT_RESTRICTED,
1170                         NET_CAPABILITY_NOT_VCN_MANAGED,
1171                         NET_CAPABILITY_NOT_VPN,
1172                         NET_CAPABILITY_TRUSTED);
1173     }
1174 
1175     @Test
1176     public void setConfiguration_null_throwsNullPointerException() throws Exception {
1177         CompletableFuture<Void> setConfigFuture = new CompletableFuture<>();
1178         assertThrows(
1179                 NullPointerException.class,
1180                 () ->
1181                         mController.setConfiguration(
1182                                 null, mExecutor, newOutcomeReceiver(setConfigFuture)));
1183     }
1184 
1185     @Test
1186     public void setConfiguration_noPermissions_throwsSecurityException() throws Exception {
1187         ThreadConfiguration configuration =
1188                 new ThreadConfiguration.Builder().setNat64Enabled(true).build();
1189         CompletableFuture<Void> setConfigFuture = new CompletableFuture<>();
1190         assertThrows(
1191                 SecurityException.class,
1192                 () -> {
1193                     mController.setConfiguration(
1194                             configuration, mExecutor, newOutcomeReceiver(setConfigFuture));
1195                 });
1196     }
1197 
1198     @Test
1199     public void registerConfigurationCallback_permissionsGranted_returnsCurrentStatus()
1200             throws Exception {
1201         CompletableFuture<ThreadConfiguration> getConfigFuture = new CompletableFuture<>();
1202         Consumer<ThreadConfiguration> callback = getConfigFuture::complete;
1203 
1204         runAsShell(
1205                 THREAD_NETWORK_PRIVILEGED,
1206                 () -> registerConfigurationCallback(mController, mExecutor, callback));
1207         assertThat(getConfigFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS))
1208                 .isEqualTo(mDefaultConfig);
1209     }
1210 
1211     @Test
1212     public void registerConfigurationCallback_noPermissions_throwsSecurityException()
1213             throws Exception {
1214         dropAllPermissions();
1215 
1216         assertThrows(
1217                 SecurityException.class,
1218                 () -> registerConfigurationCallback(mController, mExecutor, config -> {}));
1219     }
1220 
1221     @Test
1222     public void registerConfigurationCallback_returnsUpdatedConfigurations() throws Exception {
1223         CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
1224         CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
1225         ConfigurationListener listener = new ConfigurationListener(mController);
1226         ThreadConfiguration config1 =
1227                 new ThreadConfiguration.Builder()
1228                         .setBorderRouterEnabled(true)
1229                         .setNat64Enabled(true)
1230                         .build();
1231         ThreadConfiguration config2 =
1232                 new ThreadConfiguration.Builder()
1233                         .setBorderRouterEnabled(false)
1234                         .setNat64Enabled(false)
1235                         .build();
1236 
1237         try {
1238             runAsShell(
1239                     THREAD_NETWORK_PRIVILEGED,
1240                     () ->
1241                             mController.setConfiguration(
1242                                     config1, mExecutor, newOutcomeReceiver(setFuture1)));
1243             runAsShell(
1244                     THREAD_NETWORK_PRIVILEGED,
1245                     () ->
1246                             mController.setConfiguration(
1247                                     config2, mExecutor, newOutcomeReceiver(setFuture2)));
1248             setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
1249             setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
1250 
1251             listener.expectConfiguration(mDefaultConfig);
1252             listener.expectConfiguration(config1);
1253             listener.expectConfiguration(config2);
1254             listener.expectNoMoreConfiguration();
1255         } finally {
1256             listener.unregisterConfigurationCallback();
1257         }
1258     }
1259 
1260     @Test
1261     public void registerConfigurationCallback_alreadyRegistered_throwsIllegalArgumentException()
1262             throws Exception {
1263         grantPermissions(THREAD_NETWORK_PRIVILEGED);
1264 
1265         Consumer<ThreadConfiguration> callback = config -> {};
1266         registerConfigurationCallback(mController, mExecutor, callback);
1267 
1268         assertThrows(
1269                 IllegalArgumentException.class,
1270                 () -> registerConfigurationCallback(mController, mExecutor, callback));
1271     }
1272 
1273     @Test
1274     public void unregisterConfigurationCallback_noPermissions_throwsSecurityException()
1275             throws Exception {
1276         Consumer<ThreadConfiguration> callback = config -> {};
1277         runAsShell(
1278                 THREAD_NETWORK_PRIVILEGED,
1279                 () -> registerConfigurationCallback(mController, mExecutor, callback));
1280 
1281         assertThrows(
1282                 SecurityException.class,
1283                 () -> mController.unregisterConfigurationCallback(callback));
1284     }
1285 
1286     @Test
1287     public void unregisterConfigurationCallback_callbackRegistered_success() throws Exception {
1288         Consumer<ThreadConfiguration> callback = config -> {};
1289         runAsShell(
1290                 THREAD_NETWORK_PRIVILEGED,
1291                 () -> {
1292                     registerConfigurationCallback(mController, mExecutor, callback);
1293                     mController.unregisterConfigurationCallback(callback);
1294                 });
1295     }
1296 
1297     @Test
1298     public void
1299             unregisterConfigurationCallback_callbackNotRegistered_throwsIllegalArgumentException()
1300                     throws Exception {
1301         Consumer<ThreadConfiguration> callback = config -> {};
1302         assertThrows(
1303                 IllegalArgumentException.class,
1304                 () -> mController.unregisterConfigurationCallback(callback));
1305     }
1306 
1307     @Test
1308     public void unregisterConfigurationCallback_alreadyUnregistered_throwsIllegalArgumentException()
1309             throws Exception {
1310         grantPermissions(THREAD_NETWORK_PRIVILEGED);
1311 
1312         Consumer<ThreadConfiguration> callback = config -> {};
1313         registerConfigurationCallback(mController, mExecutor, callback);
1314         mController.unregisterConfigurationCallback(callback);
1315 
1316         assertThrows(
1317                 IllegalArgumentException.class,
1318                 () -> mController.unregisterConfigurationCallback(callback));
1319     }
1320 
1321     private void grantPermissions(String... permissions) {
1322         for (String permission : permissions) {
1323             mGrantedPermissions.add(permission);
1324         }
1325         String[] allPermissions = new String[mGrantedPermissions.size()];
1326         mGrantedPermissions.toArray(allPermissions);
1327         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(allPermissions);
1328     }
1329 
1330     @Test
1331     @Ignore("b/333649897, b/332195449: The 3 meshcop tests are flaky in different environments")
1332     public void meshcopService_threadEnabledButNotJoined_discoveredButNoNetwork() throws Exception {
1333         setUpTestNetwork();
1334 
1335         setEnabledAndWait(mController, true);
1336         leaveAndWait(mController);
1337 
1338         NsdServiceInfo serviceInfo =
1339                 expectServiceResolved(
1340                         MESHCOP_SERVICE_TYPE,
1341                         SERVICE_DISCOVERY_TIMEOUT_MILLIS,
1342                         s -> s.getAttributes().get("at") == null);
1343 
1344         Map<String, byte[]> txtMap = serviceInfo.getAttributes();
1345 
1346         assertThat(txtMap.get("rv")).isNotNull();
1347         assertThat(txtMap.get("tv")).isNotNull();
1348         // Border Agent State Bitmap is 32 bits
1349         assertThat(txtMap.get("sb").length).isEqualTo(4);
1350         // The 12th bit (4th bit of the second byte) for ePSKc support should be set to 1.
1351         assertThat(txtMap.get("sb")[2] & 8).isEqualTo(8);
1352     }
1353 
1354     @Test
1355     @Ignore("b/333649897: Enable this when it's not flaky at all")
1356     public void meshcopService_joinedNetwork_discoveredHasNetwork() throws Exception {
1357         setUpTestNetwork();
1358 
1359         String networkName = "TestNet" + new Random().nextInt(10_000);
1360         joinRandomizedDatasetAndWait(mController, networkName);
1361 
1362         Predicate<NsdServiceInfo> predicate =
1363                 serviceInfo ->
1364                         serviceInfo.getAttributes().get("at") != null
1365                                 && Arrays.equals(
1366                                         serviceInfo.getAttributes().get("nn"),
1367                                         networkName.getBytes(StandardCharsets.UTF_8));
1368 
1369         NsdServiceInfo resolvedService =
1370                 expectServiceResolved(
1371                         MESHCOP_SERVICE_TYPE, SERVICE_DISCOVERY_TIMEOUT_MILLIS, predicate);
1372 
1373         Map<String, byte[]> txtMap = resolvedService.getAttributes();
1374         assertThat(txtMap.get("rv")).isNotNull();
1375         assertThat(txtMap.get("tv")).isNotNull();
1376         // Border Agent State Bitmap is 32 bits
1377         assertThat(txtMap.get("sb").length).isEqualTo(4);
1378         // The 12th bit (4th bit of the second byte) for ePSKc support should be set to 1.
1379         assertThat(txtMap.get("sb")[2] & 8).isEqualTo(8);
1380         assertThat(txtMap.get("id").length).isEqualTo(16);
1381     }
1382 
1383     @Test
1384     @Ignore("b/333649897, b/332195449: The 3 meshcop tests are flaky in different environments")
1385     public void meshcopService_threadDisabled_notDiscovered() throws Exception {
1386         setUpTestNetwork();
1387         CompletableFuture<NsdServiceInfo> serviceLostFuture = new CompletableFuture<>();
1388         NsdManager.DiscoveryListener listener =
1389                 discoverForServiceLost(MESHCOP_SERVICE_TYPE, serviceLostFuture);
1390 
1391         setEnabledAndWait(mController, false);
1392 
1393         try {
1394             serviceLostFuture.get(SERVICE_LOST_TIMEOUT_MILLIS, MILLISECONDS);
1395         } catch (InterruptedException | ExecutionException | TimeoutException ignored) {
1396             // It's fine if the service lost event didn't show up. The service may not ever be
1397             // advertised.
1398         } finally {
1399             mNsdManager.stopServiceDiscovery(listener);
1400         }
1401         assertThrows(
1402                 TimeoutException.class,
1403                 () -> discoverService(MESHCOP_SERVICE_TYPE, SERVICE_LOST_TIMEOUT_MILLIS));
1404     }
1405 
1406     private static void dropAllPermissions() {
1407         getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
1408     }
1409 
1410     private static ActiveOperationalDataset newRandomizedDataset(
1411             String networkName, ThreadNetworkController controller) throws Exception {
1412         CompletableFuture<ActiveOperationalDataset> future = new CompletableFuture<>();
1413         controller.createRandomizedDataset(networkName, directExecutor(), future::complete);
1414         return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
1415     }
1416 
1417     private static boolean isAttached(ThreadNetworkController controller) throws Exception {
1418         return ThreadNetworkController.isAttached(getDeviceRole(controller));
1419     }
1420 
1421     private static int getDeviceRole(ThreadNetworkController controller) throws Exception {
1422         CompletableFuture<Integer> future = new CompletableFuture<>();
1423         StateCallback callback = future::complete;
1424         runAsShell(
1425                 ACCESS_NETWORK_STATE,
1426                 () -> controller.registerStateCallback(directExecutor(), callback));
1427         try {
1428             return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
1429         } finally {
1430             runAsShell(ACCESS_NETWORK_STATE, () -> controller.unregisterStateCallback(callback));
1431         }
1432     }
1433 
1434     private static int waitForAttachedState(ThreadNetworkController controller) throws Exception {
1435         List<Integer> attachedRoles = new ArrayList<>();
1436         attachedRoles.add(DEVICE_ROLE_CHILD);
1437         attachedRoles.add(DEVICE_ROLE_ROUTER);
1438         attachedRoles.add(DEVICE_ROLE_LEADER);
1439         return waitForStateAnyOf(controller, attachedRoles);
1440     }
1441 
1442     private static int waitForStateAnyOf(
1443             ThreadNetworkController controller, List<Integer> deviceRoles) throws Exception {
1444         CompletableFuture<Integer> future = new CompletableFuture<>();
1445         StateCallback callback =
1446                 newRole -> {
1447                     if (deviceRoles.contains(newRole)) {
1448                         future.complete(newRole);
1449                     }
1450                 };
1451         controller.registerStateCallback(directExecutor(), callback);
1452         int role = future.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
1453         controller.unregisterStateCallback(callback);
1454         return role;
1455     }
1456 
1457     private static void waitForEnabledState(ThreadNetworkController controller, int state)
1458             throws Exception {
1459         CompletableFuture<Integer> future = new CompletableFuture<>();
1460         StateCallback callback =
1461                 new ThreadNetworkController.StateCallback() {
1462                     @Override
1463                     public void onDeviceRoleChanged(int r) {}
1464 
1465                     @Override
1466                     public void onThreadEnableStateChanged(int enabled) {
1467                         if (enabled == state) {
1468                             future.complete(enabled);
1469                         }
1470                     }
1471                 };
1472         runAsShell(
1473                 ACCESS_NETWORK_STATE,
1474                 () -> controller.registerStateCallback(directExecutor(), callback));
1475         future.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
1476         runAsShell(ACCESS_NETWORK_STATE, () -> controller.unregisterStateCallback(callback));
1477     }
1478 
1479     private void leave(
1480             ThreadNetworkController controller,
1481             OutcomeReceiver<Void, ThreadNetworkException> receiver) {
1482         runAsShell(THREAD_NETWORK_PRIVILEGED, () -> controller.leave(mExecutor, receiver));
1483     }
1484 
1485     private void leaveAndWait(ThreadNetworkController controller) throws Exception {
1486         CompletableFuture<Void> future = new CompletableFuture<>();
1487         leave(controller, future::complete);
1488         future.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
1489     }
1490 
1491     private void scheduleMigration(
1492             ThreadNetworkController controller,
1493             PendingOperationalDataset pendingDataset,
1494             OutcomeReceiver<Void, ThreadNetworkException> receiver) {
1495         runAsShell(
1496                 THREAD_NETWORK_PRIVILEGED,
1497                 () -> controller.scheduleMigration(pendingDataset, mExecutor, receiver));
1498     }
1499 
1500     private class EnabledStateListener {
1501         private ArrayTrackRecord<Integer> mEnabledStates = new ArrayTrackRecord<>();
1502         private final ArrayTrackRecord<Integer>.ReadHead mReadHead = mEnabledStates.newReadHead();
1503         ThreadNetworkController mController;
1504         StateCallback mCallback =
1505                 new ThreadNetworkController.StateCallback() {
1506                     @Override
1507                     public void onDeviceRoleChanged(int r) {}
1508 
1509                     @Override
1510                     public void onThreadEnableStateChanged(int enabled) {
1511                         mEnabledStates.add(enabled);
1512                     }
1513                 };
1514 
1515         EnabledStateListener(ThreadNetworkController controller) {
1516             this.mController = controller;
1517             runAsShell(
1518                     ACCESS_NETWORK_STATE,
1519                     () -> controller.registerStateCallback(mExecutor, mCallback));
1520         }
1521 
1522         public void expectThreadEnabledState(int enabled) {
1523             assertThat(mReadHead.poll(ENABLED_TIMEOUT_MILLIS, e -> (e == enabled))).isNotNull();
1524         }
1525 
1526         public void expectCallbackNotCalled() {
1527             assertThat(mReadHead.poll(CALLBACK_TIMEOUT_MILLIS, e -> true)).isNull();
1528         }
1529 
1530         public void unregisterStateCallback() {
1531             runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(mCallback));
1532         }
1533     }
1534 
1535     private class ConfigurationListener {
1536         private ArrayTrackRecord<ThreadConfiguration> mConfigurations = new ArrayTrackRecord<>();
1537         private final ArrayTrackRecord<ThreadConfiguration>.ReadHead mReadHead =
1538                 mConfigurations.newReadHead();
1539         ThreadNetworkController mController;
1540         Consumer<ThreadConfiguration> mCallback = (config) -> mConfigurations.add(config);
1541 
1542         ConfigurationListener(ThreadNetworkController controller) {
1543             this.mController = controller;
1544             runAsShell(
1545                     THREAD_NETWORK_PRIVILEGED,
1546                     () -> controller.registerConfigurationCallback(mExecutor, mCallback));
1547         }
1548 
1549         public void expectConfiguration(ThreadConfiguration config) {
1550             assertThat(mReadHead.poll(CALLBACK_TIMEOUT_MILLIS, c -> c.equals(config))).isNotNull();
1551         }
1552 
1553         public void expectNoMoreConfiguration() {
1554             assertThat(mReadHead.poll(CALLBACK_TIMEOUT_MILLIS, c -> true)).isNull();
1555         }
1556 
1557         public void unregisterConfigurationCallback() {
1558             runAsShell(
1559                     THREAD_NETWORK_PRIVILEGED,
1560                     () -> mController.unregisterConfigurationCallback(mCallback));
1561         }
1562     }
1563 
1564     private int booleanToEnabledState(boolean enabled) {
1565         return enabled ? STATE_ENABLED : STATE_DISABLED;
1566     }
1567 
1568     private void setEnabledAndWait(ThreadNetworkController controller, boolean enabled)
1569             throws Exception {
1570         CompletableFuture<Void> setFuture = new CompletableFuture<>();
1571         runAsShell(
1572                 THREAD_NETWORK_PRIVILEGED,
1573                 () -> controller.setEnabled(enabled, mExecutor, newOutcomeReceiver(setFuture)));
1574         setFuture.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
1575         waitForEnabledState(controller, booleanToEnabledState(enabled));
1576     }
1577 
1578     private void setConfigurationAndWait(
1579             ThreadNetworkController controller, ThreadConfiguration configuration)
1580             throws Exception {
1581         CompletableFuture<Void> setFuture = new CompletableFuture<>();
1582         runAsShell(
1583                 THREAD_NETWORK_PRIVILEGED,
1584                 () ->
1585                         controller.setConfiguration(
1586                                 configuration, mExecutor, newOutcomeReceiver(setFuture)));
1587         setFuture.get(SET_CONFIGURATION_TIMEOUT_MILLIS, MILLISECONDS);
1588     }
1589 
1590     private void deactivateEphemeralKeyModeAndWait(ThreadNetworkController controller)
1591             throws Exception {
1592         CompletableFuture<Void> clearFuture = new CompletableFuture<>();
1593         runAsShell(
1594                 THREAD_NETWORK_PRIVILEGED,
1595                 () ->
1596                         controller.deactivateEphemeralKeyMode(
1597                                 mExecutor, newOutcomeReceiver(clearFuture)));
1598         clearFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
1599     }
1600 
1601     private void activateEphemeralKeyModeAndWait(ThreadNetworkController controller)
1602             throws Exception {
1603         CompletableFuture<Void> startFuture = new CompletableFuture<>();
1604         runAsShell(
1605                 THREAD_NETWORK_PRIVILEGED,
1606                 () ->
1607                         controller.activateEphemeralKeyMode(
1608                                 EPHEMERAL_KEY_LIFETIME,
1609                                 mExecutor,
1610                                 newOutcomeReceiver(startFuture)));
1611         startFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
1612     }
1613 
1614     private class EphemeralKeyStateListener {
1615         private ArrayTrackRecord<Triple<Integer, String, Instant>> mEphemeralKeyStates =
1616                 new ArrayTrackRecord<>();
1617         private final ArrayTrackRecord<Triple<Integer, String, Instant>>.ReadHead mReadHead =
1618                 mEphemeralKeyStates.newReadHead();
1619         ThreadNetworkController mController;
1620         StateCallback mCallback =
1621                 new ThreadNetworkController.StateCallback() {
1622                     @Override
1623                     public void onDeviceRoleChanged(int r) {}
1624 
1625                     @Override
1626                     public void onEphemeralKeyStateChanged(
1627                             int state, String ephemeralKey, Instant expiry) {
1628                         mEphemeralKeyStates.add(new Triple<>(state, ephemeralKey, expiry));
1629                     }
1630                 };
1631 
1632         EphemeralKeyStateListener(ThreadNetworkController controller) {
1633             this.mController = controller;
1634             runAsShell(
1635                     ACCESS_NETWORK_STATE,
1636                     THREAD_NETWORK_PRIVILEGED,
1637                     () -> controller.registerStateCallback(mExecutor, mCallback));
1638         }
1639 
1640         // Expect that EphemeralKey has the expected state, and return a Triple of <state,
1641         // passcode, expiry>.
1642         public Triple<Integer, String, Instant> expectThreadEphemeralKeyMode(int state) {
1643             Triple<Integer, String, Instant> epskc =
1644                     mReadHead.poll(
1645                             ENABLED_TIMEOUT_MILLIS, e -> Objects.equals(e.getFirst(), state));
1646             assertThat(epskc).isNotNull();
1647             return epskc;
1648         }
1649 
1650         public void unregisterStateCallback() {
1651             runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(mCallback));
1652         }
1653     }
1654 
1655     private CompletableFuture joinRandomizedDataset(
1656             ThreadNetworkController controller, String networkName) throws Exception {
1657         ActiveOperationalDataset activeDataset = newRandomizedDataset(networkName, controller);
1658         CompletableFuture<Void> joinFuture = new CompletableFuture<>();
1659         runAsShell(
1660                 THREAD_NETWORK_PRIVILEGED,
1661                 () -> controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
1662         return joinFuture;
1663     }
1664 
1665     private void joinRandomizedDatasetAndWait(ThreadNetworkController controller) throws Exception {
1666         joinRandomizedDatasetAndWait(controller, "TestNet");
1667     }
1668 
1669     private void joinRandomizedDatasetAndWait(
1670             ThreadNetworkController controller, String networkName) throws Exception {
1671         CompletableFuture<Void> joinFuture = joinRandomizedDataset(controller, networkName);
1672         joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
1673         assertThat(isAttached(controller)).isTrue();
1674     }
1675 
1676     private static ActiveOperationalDataset getActiveOperationalDataset(
1677             ThreadNetworkController controller) throws Exception {
1678         CompletableFuture<ActiveOperationalDataset> future = new CompletableFuture<>();
1679         OperationalDatasetCallback callback = future::complete;
1680         runAsShell(
1681                 ACCESS_NETWORK_STATE,
1682                 THREAD_NETWORK_PRIVILEGED,
1683                 () -> controller.registerOperationalDatasetCallback(directExecutor(), callback));
1684         try {
1685             return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
1686         } finally {
1687             runAsShell(
1688                     ACCESS_NETWORK_STATE,
1689                     THREAD_NETWORK_PRIVILEGED,
1690                     () -> controller.unregisterOperationalDatasetCallback(callback));
1691         }
1692     }
1693 
1694     private static PendingOperationalDataset getPendingOperationalDataset(
1695             ThreadNetworkController controller) throws Exception {
1696         CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
1697         CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
1698         controller.registerOperationalDatasetCallback(
1699                 directExecutor(), newDatasetCallback(activeFuture, pendingFuture));
1700         return pendingFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
1701     }
1702 
1703     private static OperationalDatasetCallback newDatasetCallback(
1704             CompletableFuture<ActiveOperationalDataset> activeFuture,
1705             CompletableFuture<PendingOperationalDataset> pendingFuture) {
1706         return new OperationalDatasetCallback() {
1707             @Override
1708             public void onActiveOperationalDatasetChanged(
1709                     ActiveOperationalDataset activeOpDataset) {
1710                 activeFuture.complete(activeOpDataset);
1711             }
1712 
1713             @Override
1714             public void onPendingOperationalDatasetChanged(
1715                     PendingOperationalDataset pendingOpDataset) {
1716                 pendingFuture.complete(pendingOpDataset);
1717             }
1718         };
1719     }
1720 
1721     private void registerConfigurationCallback(
1722             ThreadNetworkController controller,
1723             Executor executor,
1724             Consumer<ThreadConfiguration> callback) {
1725         controller.registerConfigurationCallback(executor, callback);
1726         mConfigurationCallbacksToCleanUp.add(callback);
1727     }
1728 
1729     private static void assertDoesNotThrow(ThrowingRunnable runnable) {
1730         try {
1731             runnable.run();
1732         } catch (Throwable e) {
1733             fail("Should not have thrown " + e);
1734         }
1735     }
1736 
1737     // Return the first discovered service instance.
1738     private NsdServiceInfo discoverService(String serviceType) throws Exception {
1739         return discoverService(serviceType, SERVICE_DISCOVERY_TIMEOUT_MILLIS);
1740     }
1741 
1742     // Return the first discovered service instance.
1743     private NsdServiceInfo discoverService(String serviceType, int timeoutMilliseconds)
1744             throws Exception {
1745         CompletableFuture<NsdServiceInfo> serviceInfoFuture = new CompletableFuture<>();
1746         NsdManager.DiscoveryListener listener =
1747                 new DefaultDiscoveryListener() {
1748                     @Override
1749                     public void onServiceFound(NsdServiceInfo serviceInfo) {
1750                         serviceInfoFuture.complete(serviceInfo);
1751                     }
1752                 };
1753         mNsdManager.discoverServices(
1754                 serviceType,
1755                 NsdManager.PROTOCOL_DNS_SD,
1756                 mTestNetworkTracker.getNetwork(),
1757                 mExecutor,
1758                 listener);
1759         try {
1760             serviceInfoFuture.get(timeoutMilliseconds, MILLISECONDS);
1761         } finally {
1762             mNsdManager.stopServiceDiscovery(listener);
1763         }
1764 
1765         return serviceInfoFuture.get();
1766     }
1767 
1768     private NsdManager.DiscoveryListener discoverForServiceLost(
1769             String serviceType, CompletableFuture<NsdServiceInfo> serviceInfoFuture) {
1770         NsdManager.DiscoveryListener listener =
1771                 new DefaultDiscoveryListener() {
1772                     @Override
1773                     public void onServiceLost(NsdServiceInfo serviceInfo) {
1774                         serviceInfoFuture.complete(serviceInfo);
1775                     }
1776                 };
1777         mNsdManager.discoverServices(
1778                 serviceType,
1779                 NsdManager.PROTOCOL_DNS_SD,
1780                 mTestNetworkTracker.getNetwork(),
1781                 mExecutor,
1782                 listener);
1783         return listener;
1784     }
1785 
1786     private NsdServiceInfo expectServiceResolved(
1787             String serviceType, int timeoutMilliseconds, Predicate<NsdServiceInfo> predicate)
1788             throws Exception {
1789         NsdServiceInfo discoveredServiceInfo = discoverService(serviceType);
1790         CompletableFuture<NsdServiceInfo> future = new CompletableFuture<>();
1791         NsdManager.ServiceInfoCallback callback =
1792                 new DefaultServiceInfoCallback() {
1793                     @Override
1794                     public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {
1795                         if (predicate.test(serviceInfo)) {
1796                             future.complete(serviceInfo);
1797                         }
1798                     }
1799                 };
1800         mNsdManager.registerServiceInfoCallback(discoveredServiceInfo, mExecutor, callback);
1801         try {
1802             return future.get(timeoutMilliseconds, MILLISECONDS);
1803         } finally {
1804             mNsdManager.unregisterServiceInfoCallback(callback);
1805         }
1806     }
1807 
1808     private void setUpTestNetwork() {
1809         assertThat(mTestNetworkTracker).isNull();
1810         mTestNetworkTracker = new TapTestNetworkTracker(mContext, mHandlerThread.getLooper());
1811     }
1812 
1813     private void tearDownTestNetwork() throws InterruptedException {
1814         if (mTestNetworkTracker != null) {
1815             mTestNetworkTracker.tearDown();
1816         }
1817         mHandlerThread.quitSafely();
1818         mHandlerThread.join();
1819     }
1820 
1821     private static class DefaultDiscoveryListener implements NsdManager.DiscoveryListener {
1822         @Override
1823         public void onStartDiscoveryFailed(String serviceType, int errorCode) {}
1824 
1825         @Override
1826         public void onStopDiscoveryFailed(String serviceType, int errorCode) {}
1827 
1828         @Override
1829         public void onDiscoveryStarted(String serviceType) {}
1830 
1831         @Override
1832         public void onDiscoveryStopped(String serviceType) {}
1833 
1834         @Override
1835         public void onServiceFound(NsdServiceInfo serviceInfo) {}
1836 
1837         @Override
1838         public void onServiceLost(NsdServiceInfo serviceInfo) {}
1839     }
1840 
1841     private static class DefaultServiceInfoCallback implements NsdManager.ServiceInfoCallback {
1842         @Override
1843         public void onServiceInfoCallbackRegistrationFailed(int errorCode) {}
1844 
1845         @Override
1846         public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {}
1847 
1848         @Override
1849         public void onServiceLost() {}
1850 
1851         @Override
1852         public void onServiceInfoCallbackUnregistered() {}
1853     }
1854 
1855     @Test
1856     @RequiresFlagsEnabled({Flags.FLAG_CHANNEL_MAX_POWERS_ENABLED})
1857     public void setChannelMaxPowers_withPrivilegedPermission_success() throws Exception {
1858         CompletableFuture<Void> powerFuture = new CompletableFuture<>();
1859 
1860         runAsShell(
1861                 THREAD_NETWORK_PRIVILEGED,
1862                 () ->
1863                         mController.setChannelMaxPowers(
1864                                 CHANNEL_MAX_POWERS, mExecutor, newOutcomeReceiver(powerFuture)));
1865 
1866         try {
1867             assertThat(powerFuture.get()).isNull();
1868         } catch (ExecutionException exception) {
1869             ThreadNetworkException thrown = (ThreadNetworkException) exception.getCause();
1870             assertThat(thrown.getErrorCode()).isEqualTo(ERROR_UNSUPPORTED_FEATURE);
1871         }
1872     }
1873 
1874     @Test
1875     @RequiresFlagsEnabled({Flags.FLAG_CHANNEL_MAX_POWERS_ENABLED})
1876     public void setChannelMaxPowers_withoutPrivilegedPermission_throwsSecurityException()
1877             throws Exception {
1878         dropAllPermissions();
1879 
1880         assertThrows(
1881                 SecurityException.class,
1882                 () -> mController.setChannelMaxPowers(CHANNEL_MAX_POWERS, mExecutor, v -> {}));
1883     }
1884 
1885     @Test
1886     @RequiresFlagsEnabled({Flags.FLAG_CHANNEL_MAX_POWERS_ENABLED})
1887     public void setChannelMaxPowers_invalidChannel_throwsIllegalArgumentException() {
1888         final SparseIntArray INVALID_CHANNEL_ARRAY =
1889                 new SparseIntArray() {
1890                     {
1891                         put(INVALID_CHANNEL, VALID_POWER);
1892                     }
1893                 };
1894 
1895         assertThrows(
1896                 IllegalArgumentException.class,
1897                 () -> mController.setChannelMaxPowers(new SparseIntArray(), mExecutor, v -> {}));
1898         assertThrows(
1899                 IllegalArgumentException.class,
1900                 () -> mController.setChannelMaxPowers(INVALID_CHANNEL_ARRAY, mExecutor, v -> {}));
1901     }
1902 }
1903