[thread] Support non-BR mode
This commit adds a resource overlay config for OEMs to disable the Border Router feature by default. Also updates the tests to cover both BR and non-BR use cases Bug: 401654864 Change-Id: I7dd4363836269a08853b58304f62fe52f9021a9e
This commit is contained in:
parent
70ede3260c
commit
82988c3d98
13 changed files with 306 additions and 51 deletions
|
|
@ -26,6 +26,10 @@
|
|||
-->
|
||||
<bool name="config_thread_default_enabled">true</bool>
|
||||
|
||||
<!-- Sets to {@code true} to enable Thread Border Router on the device by default.
|
||||
-->
|
||||
<bool name="config_thread_border_router_default_enabled">false</bool>
|
||||
|
||||
<!-- Whether to use location APIs in the algorithm to determine country code or not.
|
||||
If disabled, will use other sources (telephony, wifi, etc) to determine device location for
|
||||
Thread Network regulatory purposes.
|
||||
|
|
|
|||
|
|
@ -227,8 +227,8 @@ public final class ThreadNetworkController {
|
|||
* specific error:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link ThreadNetworkException#ERROR_FAILED_PRECONDITION} when this device is not
|
||||
* attached to Thread network
|
||||
* <li>{@link ThreadNetworkException#ERROR_FAILED_PRECONDITION} when this device is not a
|
||||
* Border Router or not attached to Thread network
|
||||
* <li>{@link ThreadNetworkException#ERROR_BUSY} when ephemeral key mode is already activated
|
||||
* on the device, caller can recover from this error when the ephemeral key mode gets
|
||||
* deactivated
|
||||
|
|
@ -267,7 +267,8 @@ public final class ThreadNetworkController {
|
|||
* connection will be terminated.
|
||||
*
|
||||
* <p>On success, {@link OutcomeReceiver#onResult} of {@code receiver} is called. The call will
|
||||
* always succeed if the device is not in ephemeral key mode.
|
||||
* always succeed if the device is not in ephemeral key mode. It returns an error {@link
|
||||
* ThreadNetworkException#ERROR_FAILED_PRECONDITION} if this device is not a Border Router.
|
||||
*
|
||||
* @param executor the executor to execute {@code receiver}
|
||||
* @param receiver the receiver to receive the result of this operation
|
||||
|
|
|
|||
|
|
@ -879,10 +879,8 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
|
||||
final var scoreBuilder = new NetworkScore.Builder();
|
||||
|
||||
if (isBorderRouterMode()) {
|
||||
netCapsBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK);
|
||||
scoreBuilder.setKeepConnectedReason(NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK);
|
||||
}
|
||||
netCapsBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK);
|
||||
scoreBuilder.setKeepConnectedReason(NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK);
|
||||
|
||||
return new NetworkAgent(
|
||||
mContext,
|
||||
|
|
@ -890,7 +888,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
LOG.getTag(),
|
||||
netCapsBuilder.build(),
|
||||
getTunIfLinkProperties(),
|
||||
isBorderRouterMode() ? newLocalNetworkConfig() : null,
|
||||
newLocalNetworkConfig(),
|
||||
scoreBuilder.build(),
|
||||
new NetworkAgentConfig.Builder().build(),
|
||||
mNetworkProvider) {
|
||||
|
|
@ -899,9 +897,8 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
@Override
|
||||
public void onNetworkUnwanted() {
|
||||
LOG.i("Thread network is unwanted by ConnectivityService");
|
||||
if (!isBorderRouterMode()) {
|
||||
leave(false /* eraseDataset */, new LoggingOperationReceiver("leave"));
|
||||
}
|
||||
// TODO(b/374037595): leave() the current network when the new APIs for mobile
|
||||
// is available
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ public class ThreadPersistentSettings {
|
|||
* this config is missing.
|
||||
*/
|
||||
private static final Key<Boolean> CONFIG_BORDER_ROUTER_ENABLED =
|
||||
new Key<>("config_border_router_enabled", true);
|
||||
new Key<>("config_border_router_enabled", false);
|
||||
|
||||
/** Stores the Thread NAT64 feature toggle state, true for enabled and false for disabled. */
|
||||
private static final Key<Boolean> CONFIG_NAT64_ENABLED =
|
||||
|
|
@ -123,10 +123,23 @@ public class ThreadPersistentSettings {
|
|||
readFromStoreFile();
|
||||
synchronized (mLock) {
|
||||
if (!mSettings.containsKey(THREAD_ENABLED.key)) {
|
||||
LOG.i("\"thread_enabled\" is missing in settings file, using default value");
|
||||
put(
|
||||
THREAD_ENABLED.key,
|
||||
mResources.get().getBoolean(R.bool.config_thread_default_enabled));
|
||||
boolean enabled = mResources.get().getBoolean(R.bool.config_thread_default_enabled);
|
||||
LOG.i(
|
||||
"\"thread_enabled\" is missing in settings file, using default value: "
|
||||
+ enabled);
|
||||
put(THREAD_ENABLED.key, enabled);
|
||||
}
|
||||
|
||||
if (!mSettings.containsKey(CONFIG_BORDER_ROUTER_ENABLED.key)) {
|
||||
boolean borderRouterEnabled =
|
||||
mResources
|
||||
.get()
|
||||
.getBoolean(R.bool.config_thread_border_router_default_enabled);
|
||||
LOG.i(
|
||||
"\"thread_border_router_enabled\" is missing in settings file, using"
|
||||
+ " default value: "
|
||||
+ borderRouterEnabled);
|
||||
put(CONFIG_BORDER_ROUTER_ENABLED.key, borderRouterEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
|
|||
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assume.assumeFalse;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
|
|
@ -94,12 +96,15 @@ import org.junit.Before;
|
|||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -118,6 +123,7 @@ import java.util.function.Predicate;
|
|||
/** CTS tests for {@link ThreadNetworkController}. */
|
||||
@LargeTest
|
||||
@RequiresThreadFeature
|
||||
@RunWith(Parameterized.class)
|
||||
public class ThreadNetworkControllerTest {
|
||||
private static final int JOIN_TIMEOUT_MILLIS = 30 * 1000;
|
||||
private static final int LEAVE_TIMEOUT_MILLIS = 2_000;
|
||||
|
|
@ -134,8 +140,6 @@ public class ThreadNetworkControllerTest {
|
|||
private static final String MESHCOP_SERVICE_TYPE = "_meshcop._udp";
|
||||
private static final String THREAD_NETWORK_PRIVILEGED =
|
||||
"android.permission.THREAD_NETWORK_PRIVILEGED";
|
||||
private static final ThreadConfiguration DEFAULT_CONFIG =
|
||||
new ThreadConfiguration.Builder().build();
|
||||
private static final SparseIntArray CHANNEL_MAX_POWERS =
|
||||
new SparseIntArray() {
|
||||
{
|
||||
|
|
@ -161,6 +165,22 @@ public class ThreadNetworkControllerTest {
|
|||
private final List<Consumer<ThreadConfiguration>> mConfigurationCallbacksToCleanUp =
|
||||
new ArrayList<>();
|
||||
|
||||
public final boolean mIsBorderRouterEnabled;
|
||||
private final ThreadConfiguration mDefaultConfig;
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection configArguments() {
|
||||
return Arrays.asList(new Object[][] {{false}, {true}});
|
||||
}
|
||||
|
||||
public ThreadNetworkControllerTest(boolean isBorderRouterEnabled) {
|
||||
mIsBorderRouterEnabled = isBorderRouterEnabled;
|
||||
mDefaultConfig =
|
||||
new ThreadConfiguration.Builder()
|
||||
.setBorderRouterEnabled(isBorderRouterEnabled)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mController =
|
||||
|
|
@ -175,8 +195,10 @@ public class ThreadNetworkControllerTest {
|
|||
mHandlerThread.start();
|
||||
|
||||
setEnabledAndWait(mController, true);
|
||||
setConfigurationAndWait(mController, DEFAULT_CONFIG);
|
||||
deactivateEphemeralKeyModeAndWait(mController);
|
||||
setConfigurationAndWait(mController, mDefaultConfig);
|
||||
if (mDefaultConfig.isBorderRouterEnabled()) {
|
||||
deactivateEphemeralKeyModeAndWait(mController);
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
|
|
@ -185,7 +207,7 @@ public class ThreadNetworkControllerTest {
|
|||
setEnabledAndWait(mController, true);
|
||||
leaveAndWait(mController);
|
||||
tearDownTestNetwork();
|
||||
setConfigurationAndWait(mController, DEFAULT_CONFIG);
|
||||
setConfigurationAndWait(mController, mDefaultConfig);
|
||||
for (Consumer<ThreadConfiguration> configurationCallback :
|
||||
mConfigurationCallbacksToCleanUp) {
|
||||
try {
|
||||
|
|
@ -197,7 +219,9 @@ public class ThreadNetworkControllerTest {
|
|||
}
|
||||
}
|
||||
mConfigurationCallbacksToCleanUp.clear();
|
||||
deactivateEphemeralKeyModeAndWait(mController);
|
||||
if (mDefaultConfig.isBorderRouterEnabled()) {
|
||||
deactivateEphemeralKeyModeAndWait(mController);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -573,7 +597,7 @@ public class ThreadNetworkControllerTest {
|
|||
@Override
|
||||
public void onActiveOperationalDatasetChanged(
|
||||
ActiveOperationalDataset activeDataset) {
|
||||
if (activeDataset.equals(activeDataset2)) {
|
||||
if (Objects.equals(activeDataset, activeDataset2)) {
|
||||
dataset2IsApplied.complete(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -843,6 +867,7 @@ public class ThreadNetworkControllerTest {
|
|||
@Test
|
||||
@RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
|
||||
public void activateEphemeralKeyMode_withPrivilegedPermission_succeeds() throws Exception {
|
||||
assumeTrue(mDefaultConfig.isBorderRouterEnabled());
|
||||
joinRandomizedDatasetAndWait(mController);
|
||||
CompletableFuture<Void> startFuture = new CompletableFuture<>();
|
||||
|
||||
|
|
@ -861,6 +886,7 @@ public class ThreadNetworkControllerTest {
|
|||
@RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
|
||||
public void activateEphemeralKeyMode_withoutPrivilegedPermission_throwsSecurityException()
|
||||
throws Exception {
|
||||
assumeTrue(mDefaultConfig.isBorderRouterEnabled());
|
||||
dropAllPermissions();
|
||||
|
||||
assertThrows(
|
||||
|
|
@ -874,6 +900,7 @@ public class ThreadNetworkControllerTest {
|
|||
@RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
|
||||
public void activateEphemeralKeyMode_withZeroLifetime_throwsIllegalArgumentException()
|
||||
throws Exception {
|
||||
assumeTrue(mDefaultConfig.isBorderRouterEnabled());
|
||||
grantPermissions(THREAD_NETWORK_PRIVILEGED);
|
||||
|
||||
assertThrows(
|
||||
|
|
@ -885,6 +912,7 @@ public class ThreadNetworkControllerTest {
|
|||
@RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
|
||||
public void activateEphemeralKeyMode_withInvalidLargeLifetime_throwsIllegalArgumentException()
|
||||
throws Exception {
|
||||
assumeTrue(mDefaultConfig.isBorderRouterEnabled());
|
||||
grantPermissions(THREAD_NETWORK_PRIVILEGED);
|
||||
Duration lifetime = mController.getMaxEphemeralKeyLifetime().plusMillis(1);
|
||||
|
||||
|
|
@ -897,6 +925,7 @@ public class ThreadNetworkControllerTest {
|
|||
@RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
|
||||
public void activateEphemeralKeyMode_concurrentRequests_secondOneFailsWithBusyError()
|
||||
throws Exception {
|
||||
assumeTrue(mDefaultConfig.isBorderRouterEnabled());
|
||||
joinRandomizedDatasetAndWait(mController);
|
||||
CompletableFuture<Void> future1 = new CompletableFuture<>();
|
||||
CompletableFuture<Void> future2 = new CompletableFuture<>();
|
||||
|
|
@ -945,6 +974,7 @@ public class ThreadNetworkControllerTest {
|
|||
@RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
|
||||
public void deactivateEphemeralKeyMode_withoutPrivilegedPermission_throwsSecurityException()
|
||||
throws Exception {
|
||||
assumeTrue(mDefaultConfig.isBorderRouterEnabled());
|
||||
dropAllPermissions();
|
||||
|
||||
assertThrows(
|
||||
|
|
@ -956,9 +986,7 @@ public class ThreadNetworkControllerTest {
|
|||
@RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
|
||||
public void deactivateEphemeralKeyMode_notBorderRouter_failsWithFailedPrecondition()
|
||||
throws Exception {
|
||||
setConfigurationAndWait(
|
||||
mController,
|
||||
new ThreadConfiguration.Builder().setBorderRouterEnabled(false).build());
|
||||
assumeFalse(mDefaultConfig.isBorderRouterEnabled());
|
||||
grantPermissions(THREAD_NETWORK_PRIVILEGED);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
|
|
@ -975,6 +1003,7 @@ public class ThreadNetworkControllerTest {
|
|||
@Test
|
||||
@RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
|
||||
public void subscribeEpskcState_permissionsGranted_returnsCurrentState() throws Exception {
|
||||
assumeTrue(mDefaultConfig.isBorderRouterEnabled());
|
||||
CompletableFuture<Integer> stateFuture = new CompletableFuture<>();
|
||||
CompletableFuture<String> ephemeralKeyFuture = new CompletableFuture<>();
|
||||
CompletableFuture<Instant> expiryFuture = new CompletableFuture<>();
|
||||
|
|
@ -1011,6 +1040,7 @@ public class ThreadNetworkControllerTest {
|
|||
@RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
|
||||
public void subscribeEpskcState_withoutThreadPriviledgedPermission_returnsNullEphemeralKey()
|
||||
throws Exception {
|
||||
assumeTrue(mDefaultConfig.isBorderRouterEnabled());
|
||||
CompletableFuture<Integer> stateFuture = new CompletableFuture<>();
|
||||
CompletableFuture<String> ephemeralKeyFuture = new CompletableFuture<>();
|
||||
CompletableFuture<Instant> expiryFuture = new CompletableFuture<>();
|
||||
|
|
@ -1050,6 +1080,7 @@ public class ThreadNetworkControllerTest {
|
|||
@Test
|
||||
@RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
|
||||
public void subscribeEpskcState_ephemralKeyStateChanged_returnsUpdatedState() throws Exception {
|
||||
assumeTrue(mDefaultConfig.isBorderRouterEnabled());
|
||||
EphemeralKeyStateListener listener = new EphemeralKeyStateListener(mController);
|
||||
joinRandomizedDatasetAndWait(mController);
|
||||
|
||||
|
|
@ -1068,6 +1099,7 @@ public class ThreadNetworkControllerTest {
|
|||
@Test
|
||||
@RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
|
||||
public void subscribeEpskcState_epskcEnabled_returnsSameExpiry() throws Exception {
|
||||
assumeTrue(mDefaultConfig.isBorderRouterEnabled());
|
||||
EphemeralKeyStateListener listener1 = new EphemeralKeyStateListener(mController);
|
||||
Triple<Integer, String, Instant> epskc1;
|
||||
try {
|
||||
|
|
@ -1173,7 +1205,7 @@ public class ThreadNetworkControllerTest {
|
|||
THREAD_NETWORK_PRIVILEGED,
|
||||
() -> registerConfigurationCallback(mController, mExecutor, callback));
|
||||
assertThat(getConfigFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS))
|
||||
.isEqualTo(DEFAULT_CONFIG);
|
||||
.isEqualTo(mDefaultConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -1216,7 +1248,7 @@ public class ThreadNetworkControllerTest {
|
|||
setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
|
||||
setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
|
||||
|
||||
listener.expectConfiguration(DEFAULT_CONFIG);
|
||||
listener.expectConfiguration(mDefaultConfig);
|
||||
listener.expectConfiguration(config1);
|
||||
listener.expectConfiguration(config2);
|
||||
listener.expectNoMoreConfiguration();
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ package android.net.thread;
|
|||
import static android.net.InetAddresses.parseNumericAddress;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.DEFAULT_DATASET;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.buildIcmpv4EchoReply;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.enableThreadAndJoinNetwork;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.enableBorderRouterAndJoinNetwork;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv4Packet;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv6Packet;
|
||||
|
|
@ -128,7 +128,7 @@ public class BorderRoutingTest {
|
|||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
enableThreadAndJoinNetwork(DEFAULT_DATASET);
|
||||
enableBorderRouterAndJoinNetwork(DEFAULT_DATASET);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
|
|||
import static android.net.thread.utils.IntegrationTestUtils.SERVICE_DISCOVERY_TIMEOUT;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.discoverForServiceLost;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.discoverService;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.joinNetworkAndWait;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.joinNetworkAndWaitForOmr;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.resolveService;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.resolveServiceUntil;
|
||||
|
|
@ -115,6 +116,8 @@ public class ServiceDiscoveryTest {
|
|||
public void setUp() throws Exception {
|
||||
mController.setEnabledAndWait(true);
|
||||
mController.leaveAndWait();
|
||||
var config = new ThreadConfiguration.Builder().setBorderRouterEnabled(true).build();
|
||||
mController.setConfigurationAndWait(config);
|
||||
mController.joinAndWait(DEFAULT_DATASET);
|
||||
mNsdManager = mContext.getSystemService(NsdManager.class);
|
||||
|
||||
|
|
@ -157,6 +160,143 @@ public class ServiceDiscoveryTest {
|
|||
mController.leaveAndWait();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void advertisingProxy_borderRouterDisabled_clientServiceRemovedWhenLeaveIsCalled()
|
||||
throws Exception {
|
||||
/*
|
||||
* <pre>
|
||||
* Topology:
|
||||
* Thread
|
||||
* SRP Server / AD Proxy -------------- SRP Client
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
// The Border Router / SRP Server mode can only be changed when Thread is disconnected
|
||||
mController.leaveAndWait();
|
||||
var config = new ThreadConfiguration.Builder().setBorderRouterEnabled(false).build();
|
||||
mController.setConfigurationAndWait(config);
|
||||
mController.joinAndWait(DEFAULT_DATASET);
|
||||
|
||||
FullThreadDevice srpClient = mFtds.get(0);
|
||||
joinNetworkAndWait(srpClient, DEFAULT_DATASET);
|
||||
srpClient.setSrpHostname("thread-srp-client-host");
|
||||
srpClient.setSrpHostAddresses(List.of(srpClient.getMlEid()));
|
||||
srpClient.addSrpService(
|
||||
"thread-srp-client-service",
|
||||
"_matter._tcp",
|
||||
List.of("_sub1", "_sub2"),
|
||||
12345 /* port */,
|
||||
Map.of("key1", bytes(1), "key2", bytes(2)));
|
||||
NsdServiceInfo discoveredService = discoverService(mNsdManager, "_matter._tcp");
|
||||
assertThat(discoveredService).isNotNull();
|
||||
|
||||
CompletableFuture<NsdServiceInfo> serviceLostFuture = new CompletableFuture<>();
|
||||
NsdManager.DiscoveryListener listener =
|
||||
discoverForServiceLost(mNsdManager, "_matter._tcp", serviceLostFuture);
|
||||
mController.leaveAndWait();
|
||||
|
||||
// Verify the service becomes lost.
|
||||
try {
|
||||
serviceLostFuture.get(SERVICE_DISCOVERY_TIMEOUT.toMillis(), MILLISECONDS);
|
||||
} finally {
|
||||
mNsdManager.stopServiceDiscovery(listener);
|
||||
}
|
||||
assertThrows(TimeoutException.class, () -> discoverService(mNsdManager, "_matter._tcp"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void advertisingProxy_borderRouterDisabled_clientServiceRemovedWhen2ndSrpServerEnabled()
|
||||
throws Exception {
|
||||
/*
|
||||
* <pre>
|
||||
* Topology:
|
||||
* Thread
|
||||
* SRP Server / AD Proxy -------------- SRP Client
|
||||
* (Cuttlefish) |
|
||||
* +------- 2nd SRP Server
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
// The Border Router / SRP Server mode can only be changed when Thread is disconnected
|
||||
mController.leaveAndWait();
|
||||
var config = new ThreadConfiguration.Builder().setBorderRouterEnabled(false).build();
|
||||
mController.setConfigurationAndWait(config);
|
||||
mController.joinAndWait(DEFAULT_DATASET);
|
||||
|
||||
FullThreadDevice srpClient = mFtds.get(0);
|
||||
joinNetworkAndWait(srpClient, DEFAULT_DATASET);
|
||||
srpClient.setSrpHostname("thread-srp-client-host");
|
||||
srpClient.setSrpHostAddresses(List.of(srpClient.getMlEid()));
|
||||
srpClient.addSrpService(
|
||||
"thread-srp-client-service",
|
||||
"_matter._tcp",
|
||||
List.of("_sub1", "_sub2"),
|
||||
12345 /* port */,
|
||||
Map.of("key1", bytes(1), "key2", bytes(2)));
|
||||
NsdServiceInfo discoveredService = discoverService(mNsdManager, "_matter._tcp");
|
||||
assertThat(discoveredService).isNotNull();
|
||||
|
||||
FullThreadDevice srpServer2 = mFtds.get(1);
|
||||
joinNetworkAndWait(srpServer2, DEFAULT_DATASET);
|
||||
CompletableFuture<NsdServiceInfo> serviceLostFuture = new CompletableFuture<>();
|
||||
NsdManager.DiscoveryListener listener =
|
||||
discoverForServiceLost(mNsdManager, "_matter._tcp", serviceLostFuture);
|
||||
srpServer2.setSrpServerEnabled(true);
|
||||
|
||||
// Verify the service becomes lost.
|
||||
try {
|
||||
serviceLostFuture.get(SERVICE_DISCOVERY_TIMEOUT.toMillis(), MILLISECONDS);
|
||||
} finally {
|
||||
mNsdManager.stopServiceDiscovery(listener);
|
||||
}
|
||||
assertThrows(TimeoutException.class, () -> discoverService(mNsdManager, "_matter._tcp"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void advertisingProxy_borderRouterDisabled_clientMleIdAddressIsAdvertised()
|
||||
throws Exception {
|
||||
/*
|
||||
* <pre>
|
||||
* Topology:
|
||||
* Thread
|
||||
* SRP Server / AD Proxy -------------- SRP Client
|
||||
* (Cuttlefish)
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
// The Border Router / SRP Server mode can only be changed when Thread is disconnected
|
||||
mController.leaveAndWait();
|
||||
var config = new ThreadConfiguration.Builder().setBorderRouterEnabled(false).build();
|
||||
mController.setConfigurationAndWait(config);
|
||||
mController.joinAndWait(DEFAULT_DATASET);
|
||||
|
||||
FullThreadDevice srpClient = mFtds.getFirst();
|
||||
joinNetworkAndWait(srpClient, DEFAULT_DATASET);
|
||||
srpClient.setSrpHostname("thread-srp-client-host");
|
||||
srpClient.setSrpHostAddresses(List.of(srpClient.getMlEid()));
|
||||
srpClient.addSrpService(
|
||||
"thread-srp-client-service",
|
||||
"_matter._tcp",
|
||||
List.of("_sub1", "_sub2"),
|
||||
12345 /* port */,
|
||||
Map.of("key1", bytes(1), "key2", bytes(2)));
|
||||
|
||||
NsdServiceInfo discoveredService = discoverService(mNsdManager, "_matter._tcp");
|
||||
assertThat(discoveredService).isNotNull();
|
||||
NsdServiceInfo resolvedService = resolveService(mNsdManager, discoveredService);
|
||||
assertThat(resolvedService.getServiceName()).isEqualTo("thread-srp-client-service");
|
||||
assertThat(resolvedService.getServiceType()).isEqualTo("_matter._tcp");
|
||||
assertThat(resolvedService.getPort()).isEqualTo(12345);
|
||||
assertThat(resolvedService.getAttributes())
|
||||
.comparingValuesUsing(BYTE_ARRAY_EQUALITY)
|
||||
.containsExactly("key1", bytes(1), "key2", bytes(2));
|
||||
assertThat(resolvedService.getHostname()).isEqualTo("thread-srp-client-host");
|
||||
assertThat(resolvedService.getHostAddresses()).containsExactly(srpClient.getMlEid());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void advertisingProxy_multipleSrpClientsRegisterServices_servicesResolvableByMdns()
|
||||
throws Exception {
|
||||
|
|
|
|||
|
|
@ -17,12 +17,10 @@
|
|||
package android.net.thread;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
|
||||
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_DETACHED;
|
||||
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
|
||||
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.DEFAULT_CONFIG;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.getPrefixesFromNetData;
|
||||
|
|
@ -56,6 +54,7 @@ import android.net.NetworkRequest;
|
|||
import android.net.thread.utils.FullThreadDevice;
|
||||
import android.net.thread.utils.OtDaemonController;
|
||||
import android.net.thread.utils.ThreadFeatureCheckerRule;
|
||||
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresSimulationThreadDevice;
|
||||
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
|
||||
import android.net.thread.utils.ThreadNetworkControllerWrapper;
|
||||
import android.net.thread.utils.ThreadStateListener;
|
||||
|
|
@ -63,13 +62,13 @@ import android.os.SystemClock;
|
|||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
|
|
@ -78,6 +77,7 @@ import java.net.Inet6Address;
|
|||
import java.net.InetAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
|
@ -86,7 +86,7 @@ import java.util.concurrent.Executors;
|
|||
/** Tests for E2E Android Thread integration with ot-daemon, ConnectivityService, etc.. */
|
||||
@LargeTest
|
||||
@RequiresThreadFeature
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@RunWith(Parameterized.class)
|
||||
public class ThreadIntegrationTest {
|
||||
// The byte[] buffer size for UDP tests
|
||||
private static final int UDP_BUFFER_SIZE = 1024;
|
||||
|
|
@ -109,8 +109,6 @@ public class ThreadIntegrationTest {
|
|||
+ "B9D351B40C0402A0FFF8");
|
||||
private static final ActiveOperationalDataset DEFAULT_DATASET =
|
||||
ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
|
||||
private static final ThreadConfiguration DEFAULT_CONFIG =
|
||||
new ThreadConfiguration.Builder().build();
|
||||
|
||||
private static final Inet6Address GROUP_ADDR_ALL_ROUTERS =
|
||||
(Inet6Address) InetAddresses.parseNumericAddress("ff02::2");
|
||||
|
|
@ -128,11 +126,28 @@ public class ThreadIntegrationTest {
|
|||
private OtDaemonController mOtCtl;
|
||||
private FullThreadDevice mFtd;
|
||||
|
||||
public final boolean mIsBorderRouterEnabled;
|
||||
private final ThreadConfiguration mConfig;
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection configArguments() {
|
||||
return Arrays.asList(new Object[][] {{false}, {true}});
|
||||
}
|
||||
|
||||
public ThreadIntegrationTest(boolean isBorderRouterEnabled) {
|
||||
mIsBorderRouterEnabled = isBorderRouterEnabled;
|
||||
mConfig =
|
||||
new ThreadConfiguration.Builder()
|
||||
.setBorderRouterEnabled(isBorderRouterEnabled)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mExecutor = Executors.newSingleThreadExecutor();
|
||||
mOtCtl = new OtDaemonController();
|
||||
mController.setEnabledAndWait(true);
|
||||
mController.setConfigurationAndWait(mConfig);
|
||||
mController.leaveAndWait();
|
||||
mFtd = new FullThreadDevice(10 /* nodeId */);
|
||||
}
|
||||
|
|
@ -143,7 +158,6 @@ public class ThreadIntegrationTest {
|
|||
|
||||
mController.setTestNetworkAsUpstreamAndWait(null);
|
||||
mController.leaveAndWait();
|
||||
mController.setConfigurationAndWait(DEFAULT_CONFIG);
|
||||
|
||||
mFtd.destroy();
|
||||
mExecutor.shutdownNow();
|
||||
|
|
@ -163,6 +177,7 @@ public class ThreadIntegrationTest {
|
|||
@Test
|
||||
public void otDaemonRestart_JoinedNetworkAndStopped_autoRejoinedAndTunIfStateConsistent()
|
||||
throws Exception {
|
||||
assumeTrue(mController.getConfiguration().isBorderRouterEnabled());
|
||||
mController.joinAndWait(DEFAULT_DATASET);
|
||||
|
||||
runShellCommand("stop ot-daemon");
|
||||
|
|
@ -228,6 +243,7 @@ public class ThreadIntegrationTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@RequiresSimulationThreadDevice
|
||||
public void udp_appStartEchoServer_endDeviceUdpEchoSuccess() throws Exception {
|
||||
// Topology:
|
||||
// Test App ------ thread-wpan ------ End Device
|
||||
|
|
@ -287,6 +303,7 @@ public class ThreadIntegrationTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@RequiresSimulationThreadDevice
|
||||
public void edPingsMeshLocalAddresses_oneReplyPerRequest() throws Exception {
|
||||
mController.joinAndWait(DEFAULT_DATASET);
|
||||
startFtdChild(mFtd, DEFAULT_DATASET);
|
||||
|
|
@ -361,21 +378,15 @@ public class ThreadIntegrationTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void setConfiguration_disableBorderRouter_noBrfunctionsEnabled() throws Exception {
|
||||
NetworkRequest request =
|
||||
new NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
|
||||
.build();
|
||||
@RequiresSimulationThreadDevice
|
||||
public void setConfiguration_disableBorderRouter_borderRoutingDisabled() throws Exception {
|
||||
startFtdLeader(mFtd, DEFAULT_DATASET);
|
||||
|
||||
mController.setConfigurationAndWait(
|
||||
new ThreadConfiguration.Builder().setBorderRouterEnabled(false).build());
|
||||
mController.joinAndWait(DEFAULT_DATASET);
|
||||
NetworkCapabilities caps = registerNetworkCallbackAndWait(request);
|
||||
|
||||
assertThat(caps.hasCapability(NET_CAPABILITY_LOCAL_NETWORK)).isFalse();
|
||||
assertThat(mOtCtl.getBorderRoutingState()).ignoringCase().isEqualTo("disabled");
|
||||
assertThat(mOtCtl.getSrpServerState()).ignoringCase().isNotEqualTo("disabled");
|
||||
// TODO: b/376217403 - enables / disables Border Agent at runtime
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import android.content.Context;
|
|||
import android.net.thread.utils.FullThreadDevice;
|
||||
import android.net.thread.utils.OtDaemonController;
|
||||
import android.net.thread.utils.ThreadFeatureCheckerRule;
|
||||
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresSimulationThreadDevice;
|
||||
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
|
||||
import android.net.thread.utils.ThreadNetworkControllerWrapper;
|
||||
|
||||
|
|
@ -167,6 +168,7 @@ public class ThreadNetworkShellCommandTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@RequiresSimulationThreadDevice
|
||||
public void handleOtCtlCommand_pingFtd_getValidResponse() throws Exception {
|
||||
mController.joinAndWait(DEFAULT_DATASET);
|
||||
startFtdChild(mFtd, DEFAULT_DATASET);
|
||||
|
|
|
|||
|
|
@ -239,6 +239,12 @@ public final class FullThreadDevice {
|
|||
executeCommand("udp send %s %d %s", serverAddr.getHostAddress(), serverPort, message);
|
||||
}
|
||||
|
||||
/** Sets `true` to enable SRP server on this device. */
|
||||
public void setSrpServerEnabled(boolean enabled) {
|
||||
String cmd = enabled ? "enable" : "disable";
|
||||
executeCommand("srp server " + cmd);
|
||||
}
|
||||
|
||||
/** Enables the SRP client and run in autostart mode. */
|
||||
public void autoStartSrpClient() {
|
||||
executeCommand("srp client autostart enable");
|
||||
|
|
|
|||
|
|
@ -582,6 +582,17 @@ object IntegrationTestUtils {
|
|||
return networkFuture[timeout.toSeconds(), TimeUnit.SECONDS]
|
||||
}
|
||||
|
||||
/**
|
||||
* Let the FTD join the specified Thread network and wait for it becomes a Child or Router.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(Exception::class)
|
||||
fun joinNetworkAndWait(ftd: FullThreadDevice, dataset: ActiveOperationalDataset) {
|
||||
ftd.factoryReset()
|
||||
ftd.joinNetwork(dataset)
|
||||
ftd.waitForStateAnyOf(listOf("router", "child"), JOIN_TIMEOUT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Let the FTD join the specified Thread network and wait for border routing to be available.
|
||||
*
|
||||
|
|
@ -613,6 +624,21 @@ object IntegrationTestUtils {
|
|||
controller.joinAndWait(dataset);
|
||||
}
|
||||
|
||||
/** Enables Border Router and joins the specified Thread network. */
|
||||
@JvmStatic
|
||||
fun enableBorderRouterAndJoinNetwork(dataset: ActiveOperationalDataset) {
|
||||
val context: Context = requireNotNull(ApplicationProvider.getApplicationContext());
|
||||
val controller = requireNotNull(ThreadNetworkControllerWrapper.newInstance(context));
|
||||
|
||||
// TODO: b/323301831 - This is a workaround to avoid unnecessary delay to re-form a network
|
||||
controller.leaveAndWait();
|
||||
|
||||
controller.setEnabledAndWait(true);
|
||||
val config = ThreadConfiguration.Builder().setBorderRouterEnabled(true).build();
|
||||
controller.setConfigurationAndWait(config);
|
||||
controller.joinAndWait(dataset);
|
||||
}
|
||||
|
||||
/** Leaves the Thread network and disables Thread. */
|
||||
@JvmStatic
|
||||
fun leaveNetworkAndDisableThread() {
|
||||
|
|
|
|||
|
|
@ -233,7 +233,10 @@ public final class ThreadNetworkControllerWrapper {
|
|||
public void setNat64EnabledAndWait(boolean enabled) throws Exception {
|
||||
final ThreadConfiguration config = getConfiguration();
|
||||
final ThreadConfiguration newConfig =
|
||||
new ThreadConfiguration.Builder(config).setNat64Enabled(enabled).build();
|
||||
new ThreadConfiguration.Builder(config)
|
||||
.setBorderRouterEnabled(true)
|
||||
.setNat64Enabled(enabled)
|
||||
.build();
|
||||
setConfigurationAndWait(newConfig);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import static com.google.common.io.BaseEncoding.base16;
|
|||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
|
|
@ -93,7 +94,6 @@ import android.util.AtomicFile;
|
|||
|
||||
import androidx.test.annotation.UiThreadTest;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.connectivity.resources.R;
|
||||
|
|
@ -112,6 +112,7 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.InOrder;
|
||||
|
|
@ -124,6 +125,8 @@ import java.time.Clock;
|
|||
import java.time.DateTimeException;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
|
@ -132,7 +135,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
|
||||
/** Unit tests for {@link ThreadNetworkControllerService}. */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@RunWith(Parameterized.class)
|
||||
// This test doesn't really need to run on the UI thread, but @Before and @Test annotated methods
|
||||
// need to run in the same thread because there are code in {@code ThreadNetworkControllerService}
|
||||
// checking that all its methods are running in the thread of the handler it's using. This is due
|
||||
|
|
@ -200,6 +203,17 @@ public final class ThreadNetworkControllerServiceTest {
|
|||
@Rule(order = 1)
|
||||
public final TemporaryFolder tempFolder = new TemporaryFolder();
|
||||
|
||||
private final boolean mIsBorderRouterEnabled;
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection configArguments() {
|
||||
return Arrays.asList(new Object[][] {{false}, {true}});
|
||||
}
|
||||
|
||||
public ThreadNetworkControllerServiceTest(boolean isBorderRouterEnabled) {
|
||||
mIsBorderRouterEnabled = isBorderRouterEnabled;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
|
@ -231,6 +245,8 @@ public final class ThreadNetworkControllerServiceTest {
|
|||
|
||||
when(mConnectivityResources.get()).thenReturn(mResources);
|
||||
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(true);
|
||||
when(mResources.getBoolean(eq(R.bool.config_thread_border_router_default_enabled)))
|
||||
.thenReturn(mIsBorderRouterEnabled);
|
||||
when(mResources.getBoolean(
|
||||
eq(R.bool.config_thread_srp_server_wait_for_border_routing_enabled)))
|
||||
.thenReturn(true);
|
||||
|
|
@ -920,7 +936,9 @@ public final class ThreadNetworkControllerServiceTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void initialize_upstreamNetworkRequestHasCertainTransportTypesAndCapabilities() {
|
||||
public void initialize_borderRouterEnabled_upstreamNetworkRequestHasExpectedTransportAndCaps() {
|
||||
assumeTrue(mIsBorderRouterEnabled);
|
||||
|
||||
mService.initialize();
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
|
|
@ -999,7 +1017,8 @@ public final class ThreadNetworkControllerServiceTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void activateEphemeralKeyMode_succeed() throws Exception {
|
||||
public void activateEphemeralKeyMode_borderRouterEnabled_succeed() throws Exception {
|
||||
assumeTrue(mIsBorderRouterEnabled);
|
||||
mService.initialize();
|
||||
final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
|
||||
|
||||
|
|
@ -1010,7 +1029,8 @@ public final class ThreadNetworkControllerServiceTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void deactivateEphemeralKeyMode_succeed() throws Exception {
|
||||
public void deactivateEphemeralKeyMode_borderRouterEnabled_succeed() throws Exception {
|
||||
assumeTrue(mIsBorderRouterEnabled);
|
||||
mService.initialize();
|
||||
final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue