Merge "Reduce time needed to run test." into main
diff --git a/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java
index 671b5c5..32286e1 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
@@ -33,6 +34,8 @@
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import com.android.modules.utils.build.SdkLevel;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -68,6 +71,8 @@
@Before
public void setup() {
+ // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used.
+ assumeTrue(SdkLevel.isAtLeastV());
MockitoAnnotations.initMocks(this);
mBluetoothFinderManager = new BluetoothFinderManagerSpy();
}
diff --git a/netbpfload/Android.bp b/netbpfload/Android.bp
index b71890e..f397b37 100644
--- a/netbpfload/Android.bp
+++ b/netbpfload/Android.bp
@@ -18,6 +18,22 @@
default_team: "trendy_team_fwk_core_networking",
}
+install_symlink {
+ name: "platform_ethtool_symlink",
+ symlink_target: "/apex/com.android.tethering/bin/ethtool",
+ // installed_location is relative to /system because that's the default partition for soong
+ // modules, unless we add something like `system_ext_specific: true` like in hwservicemanager.
+ installed_location: "bin/ethtool",
+}
+
+phony {
+ name: "mainline_tethering_platform_components",
+ required: [
+ "netbpfload",
+ "platform_ethtool_symlink",
+ ],
+}
+
cc_binary {
name: "netbpfload",
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index ce2c2c1..dbececf 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -78,6 +78,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import com.android.compatibility.common.util.PollingCheck
import com.android.compatibility.common.util.PropertyUtil
+import com.android.compatibility.common.util.SystemUtil
import com.android.modules.utils.build.SdkLevel.isAtLeastU
import com.android.net.module.util.DnsPacket
import com.android.net.module.util.HexDump
@@ -2106,6 +2107,89 @@
}
}
+ @Test
+ fun testServiceTypeClientRemovedAfterSocketDestroyed() {
+ val si = makeTestServiceInfo(testNetwork1.network)
+ // Register service on testNetwork1
+ val registrationRecord = NsdRegistrationRecord()
+ registerService(registrationRecord, si)
+ // Register multiple discovery requests.
+ val discoveryRecord1 = NsdDiscoveryRecord()
+ val discoveryRecord2 = NsdDiscoveryRecord()
+ val discoveryRecord3 = NsdDiscoveryRecord()
+ nsdManager.discoverServices("_test1._tcp", NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, { it.run() }, discoveryRecord1)
+ nsdManager.discoverServices("_test2._tcp", NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, { it.run() }, discoveryRecord2)
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord3)
+
+ tryTest {
+ discoveryRecord1.expectCallback<DiscoveryStarted>()
+ discoveryRecord2.expectCallback<DiscoveryStarted>()
+ discoveryRecord3.expectCallback<DiscoveryStarted>()
+ val foundInfo = discoveryRecord3.waitForServiceDiscovered(
+ serviceName, serviceType, testNetwork1.network)
+ assertEquals(testNetwork1.network, foundInfo.network)
+ // Verify that associated ServiceTypeClients has been created for testNetwork1.
+ assertTrue("No serviceTypeClients for testNetwork1.",
+ hasServiceTypeClientsForNetwork(
+ getServiceTypeClients(), testNetwork1.network))
+
+ // Disconnect testNetwork1
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ testNetwork1.close(cm)
+ }
+
+ // Verify that no ServiceTypeClients for testNetwork1.
+ discoveryRecord3.expectCallback<ServiceLost>()
+ assertFalse("Still has serviceTypeClients for testNetwork1.",
+ hasServiceTypeClientsForNetwork(
+ getServiceTypeClients(), testNetwork1.network))
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(discoveryRecord1)
+ nsdManager.stopServiceDiscovery(discoveryRecord2)
+ nsdManager.stopServiceDiscovery(discoveryRecord3)
+ discoveryRecord1.expectCallback<DiscoveryStopped>()
+ discoveryRecord2.expectCallback<DiscoveryStopped>()
+ discoveryRecord3.expectCallback<DiscoveryStopped>()
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ }
+ }
+
+ private fun hasServiceTypeClientsForNetwork(clients: List<String>, network: Network): Boolean {
+ for (client in clients) {
+ val netid = client.substring(
+ client.indexOf("network=") + "network=".length,
+ client.indexOf("interfaceIndex=") - 1)
+ if (netid == network.toString()) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * Get ServiceTypeClient logs from the system dump servicediscovery section.
+ *
+ * The sample output:
+ * ServiceTypeClient: Type{_nmt079019787._tcp.local} \
+ * SocketKey{ network=116 interfaceIndex=68 } with 1 listeners.
+ * ServiceTypeClient: Type{_nmt079019787._tcp.local} \
+ * SocketKey{ network=115 interfaceIndex=67 } with 1 listeners.
+ */
+ private fun getServiceTypeClients(): List<String> {
+ return SystemUtil.runShellCommand(
+ InstrumentationRegistry.getInstrumentation(), "dumpsys servicediscovery")
+ .split("\n").mapNotNull { line ->
+ line.indexOf("ServiceTypeClient:").let { idx ->
+ if (idx == -1) null
+ else line.substring(idx)
+ }
+ }
+ }
+
private fun buildConflictingAnnouncement(): ByteBuffer {
/*
Generated with:
@@ -2270,4 +2354,4 @@
// No duplicate addresses in the actual address list
assertEquals(actual.toSet().size, actual.size)
assertEquals(expected.toSet(), actual.toSet())
-}
\ No newline at end of file
+}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 20d457f..a5d2f4a 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -58,14 +58,11 @@
filegroup {
name: "non-connectivity-module-test",
srcs: [
- "java/android/net/Ikev2VpnProfileTest.java",
"java/android/net/IpMemoryStoreTest.java",
"java/android/net/TelephonyNetworkSpecifierTest.java",
- "java/android/net/VpnManagerTest.java",
"java/android/net/ipmemorystore/*.java",
"java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt",
"java/com/android/internal/net/NetworkUtilsInternalTest.java",
- "java/com/android/internal/net/VpnProfileTest.java",
"java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java",
"java/com/android/server/connectivity/IpConnectivityMetricsTest.java",
"java/com/android/server/connectivity/MetricsTestUtil.java",
diff --git a/tests/unit/java/android/net/Ikev2VpnProfileTest.java b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
deleted file mode 100644
index e12e961..0000000
--- a/tests/unit/java/android/net/Ikev2VpnProfileTest.java
+++ /dev/null
@@ -1,585 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
-import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V6;
-import static android.net.cts.util.IkeSessionTestUtils.getTestIkeSessionParams;
-
-import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.net.ipsec.ike.IkeKeyIdIdentification;
-import android.net.ipsec.ike.IkeTunnelConnectionParams;
-import android.os.Build;
-import android.test.mock.MockContext;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.net.VpnProfile;
-import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator;
-import com.android.net.module.util.ProxyUtils;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.math.BigInteger;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import javax.security.auth.x500.X500Principal;
-
-/** Unit tests for {@link Ikev2VpnProfile.Builder}. */
-@SmallTest
-@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-public class Ikev2VpnProfileTest {
- private static final String SERVER_ADDR_STRING = "1.2.3.4";
- private static final String IDENTITY_STRING = "Identity";
- private static final String USERNAME_STRING = "username";
- private static final String PASSWORD_STRING = "pa55w0rd";
- private static final String EXCL_LIST = "exclList";
- private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
- private static final int TEST_MTU = 1300;
-
- @Rule
- public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
-
- private final MockContext mMockContext =
- new MockContext() {
- @Override
- public String getOpPackageName() {
- return "fooPackage";
- }
- };
- private final ProxyInfo mProxy = ProxyInfo.buildDirectProxy(
- SERVER_ADDR_STRING, -1, ProxyUtils.exclusionStringAsList(EXCL_LIST));
-
- private X509Certificate mUserCert;
- private X509Certificate mServerRootCa;
- private PrivateKey mPrivateKey;
-
- @Before
- public void setUp() throws Exception {
- mServerRootCa = generateRandomCertAndKeyPair().cert;
-
- final CertificateAndKey userCertKey = generateRandomCertAndKeyPair();
- mUserCert = userCertKey.cert;
- mPrivateKey = userCertKey.key;
- }
-
- private Ikev2VpnProfile.Builder getBuilderWithDefaultOptions() {
- final Ikev2VpnProfile.Builder builder =
- new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING);
-
- builder.setBypassable(true);
- builder.setProxy(mProxy);
- builder.setMaxMtu(TEST_MTU);
- builder.setMetered(true);
-
- return builder;
- }
-
- @Test
- public void testBuildValidProfileWithOptions() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
- final Ikev2VpnProfile profile = builder.build();
- assertNotNull(profile);
-
- // Check non-auth parameters correctly stored
- assertEquals(SERVER_ADDR_STRING, profile.getServerAddr());
- assertEquals(IDENTITY_STRING, profile.getUserIdentity());
- assertEquals(mProxy, profile.getProxyInfo());
- assertTrue(profile.isBypassable());
- assertTrue(profile.isMetered());
- assertEquals(TEST_MTU, profile.getMaxMtu());
- assertEquals(Ikev2VpnProfile.DEFAULT_ALGORITHMS, profile.getAllowedAlgorithms());
- }
-
- @Test
- public void testBuildUsernamePasswordProfile() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
- final Ikev2VpnProfile profile = builder.build();
- assertNotNull(profile);
-
- assertEquals(USERNAME_STRING, profile.getUsername());
- assertEquals(PASSWORD_STRING, profile.getPassword());
- assertEquals(mServerRootCa, profile.getServerRootCaCert());
-
- assertNull(profile.getPresharedKey());
- assertNull(profile.getRsaPrivateKey());
- assertNull(profile.getUserCert());
- }
-
- @Test
- public void testBuildDigitalSignatureProfile() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
- final Ikev2VpnProfile profile = builder.build();
- assertNotNull(profile);
-
- assertEquals(profile.getUserCert(), mUserCert);
- assertEquals(mPrivateKey, profile.getRsaPrivateKey());
- assertEquals(profile.getServerRootCaCert(), mServerRootCa);
-
- assertNull(profile.getPresharedKey());
- assertNull(profile.getUsername());
- assertNull(profile.getPassword());
- }
-
- @Test
- public void testBuildPresharedKeyProfile() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthPsk(PSK_BYTES);
- final Ikev2VpnProfile profile = builder.build();
- assertNotNull(profile);
-
- assertArrayEquals(PSK_BYTES, profile.getPresharedKey());
-
- assertNull(profile.getServerRootCaCert());
- assertNull(profile.getUsername());
- assertNull(profile.getPassword());
- assertNull(profile.getRsaPrivateKey());
- assertNull(profile.getUserCert());
- }
-
- @Test
- public void testBuildWithAllowedAlgorithmsAead() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
- builder.setAuthPsk(PSK_BYTES);
-
- List<String> allowedAlgorithms =
- Arrays.asList(
- IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
- IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305);
- builder.setAllowedAlgorithms(allowedAlgorithms);
-
- final Ikev2VpnProfile profile = builder.build();
- assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
- }
-
- @Test
- public void testBuildWithAllowedAlgorithmsNormal() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
- builder.setAuthPsk(PSK_BYTES);
-
- List<String> allowedAlgorithms =
- Arrays.asList(
- IpSecAlgorithm.AUTH_HMAC_SHA512,
- IpSecAlgorithm.AUTH_AES_XCBC,
- IpSecAlgorithm.AUTH_AES_CMAC,
- IpSecAlgorithm.CRYPT_AES_CBC,
- IpSecAlgorithm.CRYPT_AES_CTR);
- builder.setAllowedAlgorithms(allowedAlgorithms);
-
- final Ikev2VpnProfile profile = builder.build();
- assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
- }
-
- @Test
- public void testSetAllowedAlgorithmsEmptyList() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- try {
- builder.setAllowedAlgorithms(new ArrayList<>());
- fail("Expected exception due to no valid algorithm set");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
- public void testSetAllowedAlgorithmsInvalidList() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
- List<String> allowedAlgorithms = new ArrayList<>();
-
- try {
- builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA256));
- fail("Expected exception due to missing encryption");
- } catch (IllegalArgumentException expected) {
- }
-
- try {
- builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.CRYPT_AES_CBC));
- fail("Expected exception due to missing authentication");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
- public void testSetAllowedAlgorithmsInsecureAlgorithm() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
- List<String> allowedAlgorithms = new ArrayList<>();
-
- try {
- builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_MD5));
- fail("Expected exception due to insecure algorithm");
- } catch (IllegalArgumentException expected) {
- }
-
- try {
- builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA1));
- fail("Expected exception due to insecure algorithm");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
- public void testBuildNoAuthMethodSet() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- try {
- builder.build();
- fail("Expected exception due to lack of auth method");
- } catch (IllegalArgumentException expected) {
- }
- }
-
-
- // TODO: Refer to Build.VERSION_CODES.SC_V2 when it's available in AOSP and mainline branch
- @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
- @Test
- public void testBuildExcludeLocalRoutesSet() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
- builder.setAuthPsk(PSK_BYTES);
- builder.setLocalRoutesExcluded(true);
-
- final Ikev2VpnProfile profile = builder.build();
- assertNotNull(profile);
- assertTrue(profile.areLocalRoutesExcluded());
-
- builder.setBypassable(false);
- try {
- builder.build();
- fail("Expected exception because excludeLocalRoutes should be set only"
- + " on the bypassable VPN");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
- public void testBuildInvalidMtu() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- try {
- builder.setMaxMtu(500);
- fail("Expected exception due to too-small MTU");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- private void verifyVpnProfileCommon(VpnProfile profile) {
- assertEquals(SERVER_ADDR_STRING, profile.server);
- assertEquals(IDENTITY_STRING, profile.ipsecIdentifier);
- assertEquals(mProxy, profile.proxy);
- assertTrue(profile.isBypassable);
- assertTrue(profile.isMetered);
- assertEquals(TEST_MTU, profile.maxMtu);
- }
-
- @Test
- public void testPskConvertToVpnProfile() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthPsk(PSK_BYTES);
- final VpnProfile profile = builder.build().toVpnProfile();
-
- verifyVpnProfileCommon(profile);
- assertEquals(Ikev2VpnProfile.encodeForIpsecSecret(PSK_BYTES), profile.ipsecSecret);
-
- // Check nothing else is set
- assertEquals("", profile.username);
- assertEquals("", profile.password);
- assertEquals("", profile.ipsecUserCert);
- assertEquals("", profile.ipsecCaCert);
- }
-
- @Test
- public void testUsernamePasswordConvertToVpnProfile() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
- final VpnProfile profile = builder.build().toVpnProfile();
-
- verifyVpnProfileCommon(profile);
- assertEquals(USERNAME_STRING, profile.username);
- assertEquals(PASSWORD_STRING, profile.password);
- assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
-
- // Check nothing else is set
- assertEquals("", profile.ipsecUserCert);
- assertEquals("", profile.ipsecSecret);
- }
-
- @Test
- public void testRsaConvertToVpnProfile() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
- final VpnProfile profile = builder.build().toVpnProfile();
-
- final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE
- + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded());
- verifyVpnProfileCommon(profile);
- assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert);
- assertEquals(
- expectedSecret,
- profile.ipsecSecret);
- assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
-
- // Check nothing else is set
- assertEquals("", profile.username);
- assertEquals("", profile.password);
- }
-
- @Test
- public void testPskFromVpnProfileDiscardsIrrelevantValues() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthPsk(PSK_BYTES);
- final VpnProfile profile = builder.build().toVpnProfile();
- profile.username = USERNAME_STRING;
- profile.password = PASSWORD_STRING;
- profile.ipsecCaCert = Ikev2VpnProfile.certificateToPemString(mServerRootCa);
- profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
-
- final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
- assertNull(result.getUsername());
- assertNull(result.getPassword());
- assertNull(result.getUserCert());
- assertNull(result.getRsaPrivateKey());
- assertNull(result.getServerRootCaCert());
- }
-
- @Test
- public void testUsernamePasswordFromVpnProfileDiscardsIrrelevantValues() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
- final VpnProfile profile = builder.build().toVpnProfile();
- profile.ipsecSecret = new String(PSK_BYTES);
- profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
-
- final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
- assertNull(result.getPresharedKey());
- assertNull(result.getUserCert());
- assertNull(result.getRsaPrivateKey());
- }
-
- @Test
- public void testRsaFromVpnProfileDiscardsIrrelevantValues() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
- final VpnProfile profile = builder.build().toVpnProfile();
- profile.username = USERNAME_STRING;
- profile.password = PASSWORD_STRING;
-
- final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
- assertNull(result.getUsername());
- assertNull(result.getPassword());
- assertNull(result.getPresharedKey());
- }
-
- @Test
- public void testPskConversionIsLossless() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthPsk(PSK_BYTES);
- final Ikev2VpnProfile ikeProfile = builder.build();
-
- assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
- }
-
- @Test
- public void testUsernamePasswordConversionIsLossless() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
- final Ikev2VpnProfile ikeProfile = builder.build();
-
- assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
- }
-
- @Test
- public void testRsaConversionIsLossless() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
- final Ikev2VpnProfile ikeProfile = builder.build();
-
- assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
- }
-
- @Test
- public void testBuildWithIkeTunConnParamsConvertToVpnProfile() throws Exception {
- // Special keyId that contains delimiter character of VpnProfile
- final byte[] keyId = "foo\0bar".getBytes();
- final IkeTunnelConnectionParams tunnelParams = new IkeTunnelConnectionParams(
- getTestIkeSessionParams(true /* testIpv6 */, new IkeKeyIdIdentification(keyId)),
- CHILD_PARAMS);
- final Ikev2VpnProfile ikev2VpnProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
- final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
-
- assertEquals(VpnProfile.TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS, vpnProfile.type);
-
- // Username, password, server, ipsecIdentifier, ipsecCaCert, ipsecSecret, ipsecUserCert and
- // getAllowedAlgorithms should not be set if IkeTunnelConnectionParams is set.
- assertEquals("", vpnProfile.server);
- assertEquals("", vpnProfile.ipsecIdentifier);
- assertEquals("", vpnProfile.username);
- assertEquals("", vpnProfile.password);
- assertEquals("", vpnProfile.ipsecCaCert);
- assertEquals("", vpnProfile.ipsecSecret);
- assertEquals("", vpnProfile.ipsecUserCert);
- assertEquals(0, vpnProfile.getAllowedAlgorithms().size());
-
- // IkeTunnelConnectionParams should stay the same.
- assertEquals(tunnelParams, vpnProfile.ikeTunConnParams);
-
- // Convert to disk-stable format and then back to Ikev2VpnProfile should be the same.
- final VpnProfile decodedVpnProfile =
- VpnProfile.decode(vpnProfile.key, vpnProfile.encode());
- final Ikev2VpnProfile convertedIkev2VpnProfile =
- Ikev2VpnProfile.fromVpnProfile(decodedVpnProfile);
- assertEquals(ikev2VpnProfile, convertedIkev2VpnProfile);
- }
-
- @Test
- public void testConversionIsLosslessWithIkeTunConnParams() throws Exception {
- final IkeTunnelConnectionParams tunnelParams =
- new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
- // Config authentication related fields is not required while building with
- // IkeTunnelConnectionParams.
- final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
- assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
- }
-
- @Test
- public void testAutomaticNattAndIpVersionConversionIsLossless() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
- builder.setAutomaticNattKeepaliveTimerEnabled(true);
- builder.setAutomaticIpVersionSelectionEnabled(true);
-
- builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
- final Ikev2VpnProfile ikeProfile = builder.build();
-
- assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
- }
-
- @Test
- public void testAutomaticNattAndIpVersionDefaults() throws Exception {
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
-
- builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
- final Ikev2VpnProfile ikeProfile = builder.build();
-
- assertEquals(false, ikeProfile.isAutomaticNattKeepaliveTimerEnabled());
- assertEquals(false, ikeProfile.isAutomaticIpVersionSelectionEnabled());
- }
-
- @Test
- public void testEquals() throws Exception {
- // Verify building without IkeTunnelConnectionParams
- final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
- builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
- assertEquals(builder.build(), builder.build());
-
- // Verify building with IkeTunnelConnectionParams
- final IkeTunnelConnectionParams tunnelParams =
- new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
- final IkeTunnelConnectionParams tunnelParams2 =
- new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
- assertEquals(new Ikev2VpnProfile.Builder(tunnelParams).build(),
- new Ikev2VpnProfile.Builder(tunnelParams2).build());
- }
-
- @Test
- public void testBuildProfileWithNullProxy() throws Exception {
- final Ikev2VpnProfile ikev2VpnProfile =
- new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
- .setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa)
- .build();
-
- // ProxyInfo should be null for the profile without setting ProxyInfo.
- assertNull(ikev2VpnProfile.getProxyInfo());
-
- // ProxyInfo should stay null after performing toVpnProfile() and fromVpnProfile()
- final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
- assertNull(vpnProfile.proxy);
-
- final Ikev2VpnProfile convertedIkev2VpnProfile = Ikev2VpnProfile.fromVpnProfile(vpnProfile);
- assertNull(convertedIkev2VpnProfile.getProxyInfo());
- }
-
- private static class CertificateAndKey {
- public final X509Certificate cert;
- public final PrivateKey key;
-
- CertificateAndKey(X509Certificate cert, PrivateKey key) {
- this.cert = cert;
- this.key = key;
- }
- }
-
- private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception {
- final Date validityBeginDate =
- new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L));
- final Date validityEndDate =
- new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L));
-
- // Generate a keypair
- final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
- keyPairGenerator.initialize(512);
- final KeyPair keyPair = keyPairGenerator.generateKeyPair();
-
- final X500Principal dnName = new X500Principal("CN=test.android.com");
- final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
- certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
- certGen.setSubjectDN(dnName);
- certGen.setIssuerDN(dnName);
- certGen.setNotBefore(validityBeginDate);
- certGen.setNotAfter(validityEndDate);
- certGen.setPublicKey(keyPair.getPublic());
- certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
-
- final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL");
- return new CertificateAndKey(cert, keyPair.getPrivate());
- }
-}
diff --git a/tests/unit/java/android/net/VpnManagerTest.java b/tests/unit/java/android/net/VpnManagerTest.java
deleted file mode 100644
index 2ab4e45..0000000
--- a/tests/unit/java/android/net/VpnManagerTest.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assume.assumeFalse;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.test.mock.MockContext;
-import android.util.SparseArray;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.net.VpnProfile;
-import com.android.internal.util.MessageUtils;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Unit tests for {@link VpnManager}. */
-@SmallTest
-@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-public class VpnManagerTest {
-
- private static final String PKG_NAME = "fooPackage";
-
- private static final String SESSION_NAME_STRING = "testSession";
- private static final String SERVER_ADDR_STRING = "1.2.3.4";
- private static final String IDENTITY_STRING = "Identity";
- private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
-
- private IVpnManager mMockService;
- private VpnManager mVpnManager;
- private final MockContext mMockContext =
- new MockContext() {
- @Override
- public String getOpPackageName() {
- return PKG_NAME;
- }
- };
-
- @Before
- public void setUp() throws Exception {
- assumeFalse("Skipping test because watches don't support VPN",
- InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WATCH));
- mMockService = mock(IVpnManager.class);
- mVpnManager = new VpnManager(mMockContext, mMockService);
- }
-
- @Test
- public void testProvisionVpnProfilePreconsented() throws Exception {
- final PlatformVpnProfile profile = getPlatformVpnProfile();
- when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
- .thenReturn(true);
-
- // Expect there to be no intent returned, as consent has already been granted.
- assertNull(mVpnManager.provisionVpnProfile(profile));
- verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
- }
-
- @Test
- public void testProvisionVpnProfileNeedsConsent() throws Exception {
- final PlatformVpnProfile profile = getPlatformVpnProfile();
- when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
- .thenReturn(false);
-
- // Expect intent to be returned, as consent has not already been granted.
- final Intent intent = mVpnManager.provisionVpnProfile(profile);
- assertNotNull(intent);
-
- final ComponentName expectedComponentName =
- ComponentName.unflattenFromString(
- "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog");
- assertEquals(expectedComponentName, intent.getComponent());
- verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
- }
-
- @Test
- public void testDeleteProvisionedVpnProfile() throws Exception {
- mVpnManager.deleteProvisionedVpnProfile();
- verify(mMockService).deleteVpnProfile(eq(PKG_NAME));
- }
-
- @Test
- public void testStartProvisionedVpnProfile() throws Exception {
- mVpnManager.startProvisionedVpnProfile();
- verify(mMockService).startVpnProfile(eq(PKG_NAME));
- }
-
- @Test
- public void testStopProvisionedVpnProfile() throws Exception {
- mVpnManager.stopProvisionedVpnProfile();
- verify(mMockService).stopVpnProfile(eq(PKG_NAME));
- }
-
- private Ikev2VpnProfile getPlatformVpnProfile() throws Exception {
- return new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
- .setBypassable(true)
- .setMaxMtu(1300)
- .setMetered(true)
- .setAuthPsk(PSK_BYTES)
- .build();
- }
-
- @Test
- public void testVpnTypesEqual() throws Exception {
- SparseArray<String> vmVpnTypes = MessageUtils.findMessageNames(
- new Class[] { VpnManager.class }, new String[]{ "TYPE_VPN_" });
- SparseArray<String> nativeVpnType = MessageUtils.findMessageNames(
- new Class[] { NativeVpnType.class }, new String[]{ "" });
-
- // TYPE_VPN_NONE = -1 is only defined in VpnManager.
- assertEquals(vmVpnTypes.size() - 1, nativeVpnType.size());
- for (int i = VpnManager.TYPE_VPN_SERVICE; i < vmVpnTypes.size(); i++) {
- assertEquals(vmVpnTypes.get(i), "TYPE_VPN_" + nativeVpnType.get(i));
- }
- }
-}
diff --git a/tests/unit/java/com/android/internal/net/VpnProfileTest.java b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
deleted file mode 100644
index acae7d2..0000000
--- a/tests/unit/java/com/android/internal/net/VpnProfileTest.java
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.net;
-
-import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
-import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V4;
-
-import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
-import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
-import static com.android.testutils.ParcelUtils.assertParcelSane;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.net.IpSecAlgorithm;
-import android.net.ipsec.ike.IkeTunnelConnectionParams;
-import android.os.Build;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/** Unit tests for {@link VpnProfile}. */
-@SmallTest
-@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-public class VpnProfileTest {
- private static final String DUMMY_PROFILE_KEY = "Test";
-
- private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23;
- private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24;
- private static final int ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE = 25;
- private static final int ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION = 26;
- private static final int ENCODED_INDEX_IKE_TUN_CONN_PARAMS = 27;
- private static final int ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED = 28;
- private static final int ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED = 29;
-
- @Test
- public void testDefaults() throws Exception {
- final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY);
-
- assertEquals(DUMMY_PROFILE_KEY, p.key);
- assertEquals("", p.name);
- assertEquals(VpnProfile.TYPE_PPTP, p.type);
- assertEquals("", p.server);
- assertEquals("", p.username);
- assertEquals("", p.password);
- assertEquals("", p.dnsServers);
- assertEquals("", p.searchDomains);
- assertEquals("", p.routes);
- assertTrue(p.mppe);
- assertEquals("", p.l2tpSecret);
- assertEquals("", p.ipsecIdentifier);
- assertEquals("", p.ipsecSecret);
- assertEquals("", p.ipsecUserCert);
- assertEquals("", p.ipsecCaCert);
- assertEquals("", p.ipsecServerCert);
- assertEquals(null, p.proxy);
- assertTrue(p.getAllowedAlgorithms() != null && p.getAllowedAlgorithms().isEmpty());
- assertFalse(p.isBypassable);
- assertFalse(p.isMetered);
- assertEquals(1360, p.maxMtu);
- assertFalse(p.areAuthParamsInline);
- assertFalse(p.isRestrictedToTestNetworks);
- assertFalse(p.excludeLocalRoutes);
- assertFalse(p.requiresInternetValidation);
- assertFalse(p.automaticNattKeepaliveTimerEnabled);
- assertFalse(p.automaticIpVersionSelectionEnabled);
- }
-
- private VpnProfile getSampleIkev2Profile(String key) {
- final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
- false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
- null /* ikeTunConnParams */, true /* mAutomaticNattKeepaliveTimerEnabled */,
- true /* automaticIpVersionSelectionEnabled */);
-
- p.name = "foo";
- p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
- p.server = "bar";
- p.username = "baz";
- p.password = "qux";
- p.dnsServers = "8.8.8.8";
- p.searchDomains = "";
- p.routes = "0.0.0.0/0";
- p.mppe = false;
- p.l2tpSecret = "";
- p.ipsecIdentifier = "quux";
- p.ipsecSecret = "quuz";
- p.ipsecUserCert = "corge";
- p.ipsecCaCert = "grault";
- p.ipsecServerCert = "garply";
- p.proxy = null;
- p.setAllowedAlgorithms(
- Arrays.asList(
- IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
- IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
- IpSecAlgorithm.AUTH_HMAC_SHA512,
- IpSecAlgorithm.CRYPT_AES_CBC));
- p.isBypassable = true;
- p.isMetered = true;
- p.maxMtu = 1350;
- p.areAuthParamsInline = true;
-
- // Not saved, but also not compared.
- p.saveLogin = true;
-
- return p;
- }
-
- private VpnProfile getSampleIkev2ProfileWithIkeTunConnParams(String key) {
- final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
- false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
- new IkeTunnelConnectionParams(IKE_PARAMS_V4, CHILD_PARAMS),
- true /* mAutomaticNattKeepaliveTimerEnabled */,
- true /* automaticIpVersionSelectionEnabled */);
-
- p.name = "foo";
- p.server = "bar";
- p.dnsServers = "8.8.8.8";
- p.searchDomains = "";
- p.routes = "0.0.0.0/0";
- p.mppe = false;
- p.proxy = null;
- p.setAllowedAlgorithms(
- Arrays.asList(
- IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
- IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
- IpSecAlgorithm.AUTH_HMAC_SHA512,
- IpSecAlgorithm.CRYPT_AES_CBC));
- p.isBypassable = true;
- p.isMetered = true;
- p.maxMtu = 1350;
- p.areAuthParamsInline = true;
-
- // Not saved, but also not compared.
- p.saveLogin = true;
-
- return p;
- }
-
- @Test
- public void testEquals() {
- assertEquals(
- getSampleIkev2Profile(DUMMY_PROFILE_KEY), getSampleIkev2Profile(DUMMY_PROFILE_KEY));
-
- final VpnProfile modified = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
- modified.maxMtu--;
- assertNotEquals(getSampleIkev2Profile(DUMMY_PROFILE_KEY), modified);
- }
-
- @Test
- public void testParcelUnparcel() {
- if (isAtLeastU()) {
- // automaticNattKeepaliveTimerEnabled, automaticIpVersionSelectionEnabled added in U.
- assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 28);
- assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 28);
- } else if (isAtLeastT()) {
- // excludeLocalRoutes, requiresPlatformValidation were added in T.
- assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 26);
- assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 26);
- } else {
- assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23);
- }
- }
-
- @Test
- public void testEncodeDecodeWithIkeTunConnParams() {
- final VpnProfile profile = getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY);
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
- assertEquals(profile, decoded);
- }
-
- @Test
- public void testEncodeDecode() {
- final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
- assertEquals(profile, decoded);
- }
-
- @Test
- public void testEncodeDecodeTooManyValues() {
- final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
- final byte[] tooManyValues =
- (new String(profile.encode()) + VpnProfile.VALUE_DELIMITER + "invalid").getBytes();
-
- assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues));
- }
-
- private String getEncodedDecodedIkev2ProfileMissingValues(int... missingIndices) {
- // Sort to ensure when we remove, we can do it from greatest first.
- Arrays.sort(missingIndices);
-
- final String encoded = new String(getSampleIkev2Profile(DUMMY_PROFILE_KEY).encode());
- final List<String> parts =
- new ArrayList<>(Arrays.asList(encoded.split(VpnProfile.VALUE_DELIMITER)));
-
- // Remove from back first to ensure indexing is consistent.
- for (int i = missingIndices.length - 1; i >= 0; i--) {
- parts.remove(missingIndices[i]);
- }
-
- return String.join(VpnProfile.VALUE_DELIMITER, parts.toArray(new String[0]));
- }
-
- @Test
- public void testEncodeDecodeInvalidNumberOfValues() {
- final String tooFewValues =
- getEncodedDecodedIkev2ProfileMissingValues(
- ENCODED_INDEX_AUTH_PARAMS_INLINE,
- ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
- ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
- ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
- ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
- ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
- ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED
- /* missingIndices */);
-
- assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()));
- }
-
- private String getEncodedDecodedIkev2ProfileWithtooFewValues() {
- return getEncodedDecodedIkev2ProfileMissingValues(
- ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
- ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
- ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
- ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
- ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
- ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED /* missingIndices */);
- }
-
- @Test
- public void testEncodeDecodeMissingIsRestrictedToTestNetworks() {
- final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
-
- // Verify decoding without isRestrictedToTestNetworks defaults to false
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
- assertFalse(decoded.isRestrictedToTestNetworks);
- }
-
- @Test
- public void testEncodeDecodeMissingExcludeLocalRoutes() {
- final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
-
- // Verify decoding without excludeLocalRoutes defaults to false
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
- assertFalse(decoded.excludeLocalRoutes);
- }
-
- @Test
- public void testEncodeDecodeMissingRequiresValidation() {
- final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
-
- // Verify decoding without requiresValidation defaults to false
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
- assertFalse(decoded.requiresInternetValidation);
- }
-
- @Test
- public void testEncodeDecodeMissingAutomaticNattKeepaliveTimerEnabled() {
- final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
-
- // Verify decoding without automaticNattKeepaliveTimerEnabled defaults to false
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
- assertFalse(decoded.automaticNattKeepaliveTimerEnabled);
- }
-
- @Test
- public void testEncodeDecodeMissingAutomaticIpVersionSelectionEnabled() {
- final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
-
- // Verify decoding without automaticIpVersionSelectionEnabled defaults to false
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
- assertFalse(decoded.automaticIpVersionSelectionEnabled);
- }
-
- @Test
- public void testEncodeDecodeLoginsNotSaved() {
- final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
- profile.saveLogin = false;
-
- final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
- assertNotEquals(profile, decoded);
-
- // Add the username/password back, everything else must be equal.
- decoded.username = profile.username;
- decoded.password = profile.password;
- assertEquals(profile, decoded);
- }
-
- @Test
- public void testClone() {
- final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
- final VpnProfile clone = profile.clone();
- assertEquals(profile, clone);
- assertNotSame(profile, clone);
- }
-}
diff --git a/thread/apex/ot-daemon.34rc b/thread/apex/ot-daemon.34rc
index 25060d1..86f6b69 100644
--- a/thread/apex/ot-daemon.34rc
+++ b/thread/apex/ot-daemon.34rc
@@ -21,5 +21,5 @@
user thread_network
group thread_network inet system
seclabel u:r:ot_daemon:s0
- socket ot-daemon/thread-wpan.sock stream 0666 thread_network thread_network
+ socket ot-daemon/thread-wpan.sock stream 0660 thread_network thread_network
override
diff --git a/thread/service/java/com/android/server/thread/NsdPublisher.java b/thread/service/java/com/android/server/thread/NsdPublisher.java
index c74c023..440c2c3 100644
--- a/thread/service/java/com/android/server/thread/NsdPublisher.java
+++ b/thread/service/java/com/android/server/thread/NsdPublisher.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.net.InetAddresses;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Handler;
@@ -33,6 +34,7 @@
import com.android.server.thread.openthread.INsdPublisher;
import com.android.server.thread.openthread.INsdStatusReceiver;
+import java.net.InetAddress;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
@@ -119,6 +121,30 @@
return serviceInfo;
}
+ @Override
+ public void registerHost(
+ String name, List<String> addresses, INsdStatusReceiver receiver, int listenerId) {
+ postRegistrationJob(
+ () -> {
+ NsdServiceInfo serviceInfo = buildServiceInfoForHost(name, addresses);
+ registerInternal(serviceInfo, receiver, listenerId, "host");
+ });
+ }
+
+ private static NsdServiceInfo buildServiceInfoForHost(
+ String name, List<String> addressStrings) {
+ NsdServiceInfo serviceInfo = new NsdServiceInfo();
+
+ serviceInfo.setHostname(name);
+ ArrayList<InetAddress> addresses = new ArrayList<>(addressStrings.size());
+ for (String addressString : addressStrings) {
+ addresses.add(InetAddresses.parseNumericAddress(addressString));
+ }
+ serviceInfo.setHostAddresses(addresses);
+
+ return serviceInfo;
+ }
+
private void registerInternal(
NsdServiceInfo serviceInfo,
INsdStatusReceiver receiver,
diff --git a/thread/tests/integration/Android.bp b/thread/tests/integration/Android.bp
index 6ba192d..9677ec5 100644
--- a/thread/tests/integration/Android.bp
+++ b/thread/tests/integration/Android.bp
@@ -31,6 +31,7 @@
"net-utils-device-common",
"net-utils-device-common-bpf",
"testables",
+ "ThreadNetworkTestUtils",
"truth",
],
libs: [
diff --git a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
new file mode 100644
index 0000000..9bc92c7
--- /dev/null
+++ b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.thread;
+
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
+import static android.net.thread.utils.IntegrationTestUtils.JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
+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.isSimulatedThreadRadioSupported;
+import static android.net.thread.utils.IntegrationTestUtils.resolveService;
+import static android.net.thread.utils.IntegrationTestUtils.resolveServiceUntil;
+import static android.net.thread.utils.IntegrationTestUtils.waitFor;
+
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.content.Context;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.net.thread.utils.FullThreadDevice;
+import android.net.thread.utils.OtDaemonController;
+import android.net.thread.utils.TapTestNetworkTracker;
+import android.os.HandlerThread;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.truth.Correspondence;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeoutException;
+
+/** Integration test cases for Service Discovery feature. */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class ServiceDiscoveryTest {
+ private static final String TAG = ServiceDiscoveryTest.class.getSimpleName();
+ private static final int NUM_FTD = 3;
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+
+ private HandlerThread mHandlerThread;
+ private ThreadNetworkController mController;
+ private NsdManager mNsdManager;
+ private TapTestNetworkTracker mTestNetworkTracker;
+ private List<FullThreadDevice> mFtds;
+
+ // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
+ private static final byte[] DEFAULT_DATASET_TLVS =
+ base16().decode(
+ "0E080000000000010000000300001335060004001FFFE002"
+ + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
+ + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
+ + "642D643961300102D9A00410A245479C836D551B9CA557F7"
+ + "B9D351B40C0402A0FFF8");
+ private static final ActiveOperationalDataset DEFAULT_DATASET =
+ ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+
+ private static final Correspondence<byte[], byte[]> BYTE_ARRAY_EQUALITY =
+ Correspondence.from(Arrays::equals, "is equivalent to");
+
+ @Before
+ public void setUp() throws Exception {
+ final ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
+ if (manager != null) {
+ mController = manager.getAllThreadNetworkControllers().get(0);
+ }
+
+ // Run the tests on only devices where the Thread feature is available.
+ assumeNotNull(mController);
+
+ // Run the tests only when the device uses simulated Thread radio.
+ assumeTrue(isSimulatedThreadRadioSupported());
+
+ // BR forms a network.
+ CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+ runAsShell(
+ PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ () -> mController.join(DEFAULT_DATASET, directExecutor(), joinFuture::complete));
+ joinFuture.get(RESTART_JOIN_TIMEOUT.toMillis(), MILLISECONDS);
+
+ mNsdManager = mContext.getSystemService(NsdManager.class);
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+
+ mTestNetworkTracker = new TapTestNetworkTracker(mContext, mHandlerThread.getLooper());
+ assertThat(mTestNetworkTracker).isNotNull();
+ runAsShell(
+ PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ NETWORK_SETTINGS,
+ () -> {
+ CompletableFuture<Void> future = new CompletableFuture<>();
+ mController.setTestNetworkAsUpstream(
+ mTestNetworkTracker.getInterfaceName(),
+ directExecutor(),
+ v -> future.complete(null));
+ future.get(5, SECONDS);
+ });
+ // Create the FTDs in setUp() so that the FTDs can be safely released in tearDown().
+ // Don't create new FTDs in test cases.
+ mFtds = new ArrayList<>();
+ for (int i = 0; i < NUM_FTD; ++i) {
+ FullThreadDevice ftd = new FullThreadDevice(10 + i /* node ID */);
+ ftd.autoStartSrpClient();
+ mFtds.add(ftd);
+ }
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mController == null) {
+ return;
+ }
+ if (!isSimulatedThreadRadioSupported()) {
+ return;
+ }
+ for (FullThreadDevice ftd : mFtds) {
+ // Clear registered SRP hosts and services
+ if (ftd.isSrpHostRegistered()) {
+ ftd.removeSrpHost();
+ }
+ ftd.destroy();
+ }
+ if (mTestNetworkTracker != null) {
+ mTestNetworkTracker.tearDown();
+ }
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ mHandlerThread.join();
+ }
+ runAsShell(
+ PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ NETWORK_SETTINGS,
+ () -> {
+ CompletableFuture<Void> setUpstreamFuture = new CompletableFuture<>();
+ CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
+ mController.setTestNetworkAsUpstream(
+ null, directExecutor(), v -> setUpstreamFuture.complete(null));
+ mController.leave(directExecutor(), v -> leaveFuture.complete(null));
+ setUpstreamFuture.get(5, SECONDS);
+ leaveFuture.get(5, SECONDS);
+ });
+ }
+
+ @Test
+ public void advertisingProxy_multipleSrpClientsRegisterServices_servicesResolvableByMdns()
+ throws Exception {
+ /*
+ * <pre>
+ * Topology:
+ * Thread
+ * Border Router -------------- Full Thread device 1
+ * (Cuttlefish) |
+ * +------ Full Thread device 2
+ * |
+ * +------ Full Thread device 3
+ * </pre>
+ */
+
+ // Creates Full Thread Devices (FTD) and let them join the network.
+ for (FullThreadDevice ftd : mFtds) {
+ ftd.joinNetwork(DEFAULT_DATASET);
+ ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ }
+
+ int randomId = new Random().nextInt(10_000);
+
+ String serviceNamePrefix = "service-" + randomId + "-";
+ String serviceTypePrefix = "_test" + randomId;
+ String hostnamePrefix = "host-" + randomId + "-";
+
+ // For every FTD, let it register an SRP service.
+ for (int i = 0; i < mFtds.size(); ++i) {
+ FullThreadDevice ftd = mFtds.get(i);
+ ftd.setSrpHostname(hostnamePrefix + i);
+ ftd.setSrpHostAddresses(List.of(ftd.getOmrAddress(), ftd.getMlEid()));
+ ftd.addSrpService(
+ serviceNamePrefix + i,
+ serviceTypePrefix + i + "._tcp",
+ List.of("_sub1", "_sub2"),
+ 12345 /* port */,
+ Map.of("key1", bytes(0x01, 0x02), "key2", bytes(i)));
+ }
+
+ // Check the advertised services are discoverable and resolvable by NsdManager
+ for (int i = 0; i < mFtds.size(); ++i) {
+ NsdServiceInfo discoveredService =
+ discoverService(mNsdManager, serviceTypePrefix + i + "._tcp");
+ assertThat(discoveredService).isNotNull();
+ NsdServiceInfo resolvedService = resolveService(mNsdManager, discoveredService);
+ assertThat(resolvedService.getServiceName()).isEqualTo(serviceNamePrefix + i);
+ assertThat(resolvedService.getServiceType()).isEqualTo(serviceTypePrefix + i + "._tcp");
+ assertThat(resolvedService.getPort()).isEqualTo(12345);
+ assertThat(resolvedService.getAttributes())
+ .comparingValuesUsing(BYTE_ARRAY_EQUALITY)
+ .containsExactly("key1", bytes(0x01, 0x02), "key2", bytes(i));
+ assertThat(resolvedService.getHostname()).isEqualTo(hostnamePrefix + i);
+ assertThat(resolvedService.getHostAddresses())
+ .containsExactly(mFtds.get(i).getOmrAddress());
+ }
+ }
+
+ @Test
+ public void advertisingProxy_srpClientUpdatesService_updatedServiceResolvableByMdns()
+ throws Exception {
+ /*
+ * <pre>
+ * Topology:
+ * Thread
+ * Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ // Creates a Full Thread Devices (FTD) and let it join the network.
+ FullThreadDevice ftd = mFtds.get(0);
+ ftd.joinNetwork(DEFAULT_DATASET);
+ ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ ftd.setSrpHostname("my-host");
+ ftd.setSrpHostAddresses(List.of((Inet6Address) parseNumericAddress("2001:db8::1")));
+ ftd.addSrpService(
+ "my-service",
+ "_test._tcp",
+ Collections.emptyList() /* subtypes */,
+ 12345 /* port */,
+ Map.of("key1", bytes(0x01, 0x02), "key2", bytes(0x03)));
+
+ // Update the host addresses
+ ftd.setSrpHostAddresses(
+ List.of(
+ (Inet6Address) parseNumericAddress("2001:db8::1"),
+ (Inet6Address) parseNumericAddress("2001:db8::2")));
+ // Update the service
+ ftd.updateSrpService(
+ "my-service", "_test._tcp", List.of("_sub3"), 11111, Map.of("key1", bytes(0x04)));
+ waitFor(ftd::isSrpHostRegistered, SERVICE_DISCOVERY_TIMEOUT);
+
+ // Check the advertised service is discoverable and resolvable by NsdManager
+ NsdServiceInfo discoveredService = discoverService(mNsdManager, "_test._tcp");
+ assertThat(discoveredService).isNotNull();
+ NsdServiceInfo resolvedService =
+ resolveServiceUntil(
+ mNsdManager,
+ discoveredService,
+ s -> s.getPort() == 11111 && s.getHostAddresses().size() == 2);
+ assertThat(resolvedService.getServiceName()).isEqualTo("my-service");
+ assertThat(resolvedService.getServiceType()).isEqualTo("_test._tcp");
+ assertThat(resolvedService.getPort()).isEqualTo(11111);
+ assertThat(resolvedService.getAttributes())
+ .comparingValuesUsing(BYTE_ARRAY_EQUALITY)
+ .containsExactly("key1", bytes(0x04));
+ assertThat(resolvedService.getHostname()).isEqualTo("my-host");
+ assertThat(resolvedService.getHostAddresses())
+ .containsExactly(
+ parseNumericAddress("2001:db8::1"), parseNumericAddress("2001:db8::2"));
+ }
+
+ @Test
+ public void advertisingProxy_srpClientUnregistersService_serviceIsNotDiscoverableByMdns()
+ throws Exception {
+ /*
+ * <pre>
+ * Topology:
+ * Thread
+ * Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ // Creates a Full Thread Devices (FTD) and let it join the network.
+ FullThreadDevice ftd = mFtds.get(0);
+ ftd.joinNetwork(DEFAULT_DATASET);
+ ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ ftd.setSrpHostname("my-host");
+ ftd.setSrpHostAddresses(
+ List.of(
+ (Inet6Address) parseNumericAddress("2001:db8::1"),
+ (Inet6Address) parseNumericAddress("2001:db8::2")));
+ ftd.addSrpService(
+ "my-service",
+ "_test._udp",
+ List.of("_sub1"),
+ 12345 /* port */,
+ Map.of("key1", bytes(0x01, 0x02), "key2", bytes(0x03)));
+ // Wait for the service to be discoverable by NsdManager.
+ assertThat(discoverService(mNsdManager, "_test._udp")).isNotNull();
+
+ // Unregister the service.
+ CompletableFuture<NsdServiceInfo> serviceLostFuture = new CompletableFuture<>();
+ NsdManager.DiscoveryListener listener =
+ discoverForServiceLost(mNsdManager, "_test._udp", serviceLostFuture);
+ ftd.removeSrpService("my-service", "_test._udp", true /* notifyServer */);
+
+ // Verify the service becomes lost.
+ try {
+ serviceLostFuture.get(SERVICE_DISCOVERY_TIMEOUT.toMillis(), MILLISECONDS);
+ } finally {
+ mNsdManager.stopServiceDiscovery(listener);
+ }
+ assertThrows(TimeoutException.class, () -> discoverService(mNsdManager, "_test._udp"));
+ }
+
+ private static byte[] bytes(int... byteInts) {
+ byte[] bytes = new byte[byteInts.length];
+ for (int i = 0; i < byteInts.length; ++i) {
+ bytes[i] = (byte) byteInts[i];
+ }
+ return bytes;
+ }
+}
diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
index 6cb1675..6306a65 100644
--- a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
@@ -15,6 +15,7 @@
*/
package android.net.thread.utils;
+import static android.net.thread.utils.IntegrationTestUtils.SERVICE_DISCOVERY_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
import static com.google.common.io.BaseEncoding.base16;
@@ -25,15 +26,19 @@
import android.net.IpPrefix;
import android.net.thread.ActiveOperationalDataset;
+import com.google.errorprone.annotations.FormatMethod;
+
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Inet6Address;
+import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -191,7 +196,7 @@
public void udpBind(Inet6Address address, int port) {
udpClose();
udpOpen();
- executeCommand(String.format("udp bind %s %d", address.getHostAddress(), port));
+ executeCommand("udp bind %s %d", address.getHostAddress(), port);
}
/** Returns the message received on the UDP socket. */
@@ -204,6 +209,117 @@
return matcher.group(4);
}
+ /** Enables the SRP client and run in autostart mode. */
+ public void autoStartSrpClient() {
+ executeCommand("srp client autostart enable");
+ }
+
+ /** Sets the hostname (e.g. "MyHost") for the SRP client. */
+ public void setSrpHostname(String hostname) {
+ executeCommand("srp client host name " + hostname);
+ }
+
+ /** Sets the host addresses for the SRP client. */
+ public void setSrpHostAddresses(List<Inet6Address> addresses) {
+ executeCommand(
+ "srp client host address "
+ + String.join(
+ " ",
+ addresses.stream().map(Inet6Address::getHostAddress).toList()));
+ }
+
+ /** Removes the SRP host */
+ public void removeSrpHost() {
+ executeCommand("srp client host remove 1 1");
+ }
+
+ /**
+ * Adds an SRP service for the SRP client and wait for the registration to complete.
+ *
+ * @param serviceName the service name like "MyService"
+ * @param serviceType the service type like "_test._tcp"
+ * @param subtypes the service subtypes like "_sub1"
+ * @param port the port number in range [1, 65535]
+ * @param txtMap the map of TXT names and values
+ * @throws TimeoutException if the service isn't registered within timeout
+ */
+ public void addSrpService(
+ String serviceName,
+ String serviceType,
+ List<String> subtypes,
+ int port,
+ Map<String, byte[]> txtMap)
+ throws TimeoutException {
+ StringBuilder fullServiceType = new StringBuilder(serviceType);
+ for (String subtype : subtypes) {
+ fullServiceType.append(",").append(subtype);
+ }
+ executeCommand(
+ "srp client service add %s %s %d %d %d %s",
+ serviceName,
+ fullServiceType,
+ port,
+ 0 /* priority */,
+ 0 /* weight */,
+ txtMapToHexString(txtMap));
+ waitFor(() -> isSrpServiceRegistered(serviceName, serviceType), SERVICE_DISCOVERY_TIMEOUT);
+ }
+
+ /**
+ * Removes an SRP service for the SRP client.
+ *
+ * @param serviceName the service name like "MyService"
+ * @param serviceType the service type like "_test._tcp"
+ * @param notifyServer whether to notify SRP server about the removal
+ */
+ public void removeSrpService(String serviceName, String serviceType, boolean notifyServer) {
+ String verb = notifyServer ? "remove" : "clear";
+ executeCommand("srp client service %s %s %s", verb, serviceName, serviceType);
+ }
+
+ /**
+ * Updates an existing SRP service for the SRP client.
+ *
+ * <p>This is essentially a 'remove' and an 'add' on the SRP client's side.
+ *
+ * @param serviceName the service name like "MyService"
+ * @param serviceType the service type like "_test._tcp"
+ * @param subtypes the service subtypes like "_sub1"
+ * @param port the port number in range [1, 65535]
+ * @param txtMap the map of TXT names and values
+ * @throws TimeoutException if the service isn't updated within timeout
+ */
+ public void updateSrpService(
+ String serviceName,
+ String serviceType,
+ List<String> subtypes,
+ int port,
+ Map<String, byte[]> txtMap)
+ throws TimeoutException {
+ removeSrpService(serviceName, serviceType, false /* notifyServer */);
+ addSrpService(serviceName, serviceType, subtypes, port, txtMap);
+ }
+
+ /** Checks if an SRP service is registered. */
+ public boolean isSrpServiceRegistered(String serviceName, String serviceType) {
+ List<String> lines = executeCommand("srp client service");
+ for (String line : lines) {
+ if (line.contains(serviceName) && line.contains(serviceType)) {
+ return line.contains("Registered");
+ }
+ }
+ return false;
+ }
+
+ /** Checks if an SRP host is registered. */
+ public boolean isSrpHostRegistered() {
+ List<String> lines = executeCommand("srp client host");
+ for (String line : lines) {
+ return line.contains("Registered");
+ }
+ return false;
+ }
+
/** Runs the "factoryreset" command on the device. */
public void factoryReset() {
try {
@@ -240,6 +356,11 @@
ping(address, null, 100 /* size */, 1 /* count */);
}
+ @FormatMethod
+ private List<String> executeCommand(String commandFormat, Object... args) {
+ return executeCommand(String.format(commandFormat, args));
+ }
+
private List<String> executeCommand(String command) {
try {
mWriter.write(command + "\n");
@@ -263,7 +384,7 @@
if (line.equals("Done")) {
break;
}
- if (line.startsWith("Error:")) {
+ if (line.startsWith("Error")) {
fail("ot-cli-ftd reported an error: " + line);
}
if (!line.startsWith("> ")) {
@@ -272,4 +393,27 @@
}
return result;
}
+
+ private static String txtMapToHexString(Map<String, byte[]> txtMap) {
+ if (txtMap == null) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder();
+ for (Map.Entry<String, byte[]> entry : txtMap.entrySet()) {
+ int length = entry.getKey().length() + entry.getValue().length + 1;
+ sb.append(String.format("%02x", length));
+ sb.append(toHexString(entry.getKey()));
+ sb.append(toHexString("="));
+ sb.append(toHexString(entry.getValue()));
+ }
+ return sb.toString();
+ }
+
+ private static String toHexString(String s) {
+ return toHexString(s.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private static String toHexString(byte[] bytes) {
+ return base16().encode(bytes);
+ }
}
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
index 74251a6..6e70d24 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -23,12 +23,18 @@
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
import android.net.TestNetworkInterface;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
import android.net.thread.ThreadNetworkController;
import android.os.Handler;
import android.os.SystemClock;
import android.os.SystemProperties;
+import androidx.annotation.NonNull;
+
import com.android.net.module.util.Struct;
import com.android.net.module.util.structs.Icmpv6Header;
import com.android.net.module.util.structs.Ipv6Header;
@@ -51,6 +57,7 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -66,6 +73,7 @@
public static final Duration JOIN_TIMEOUT = Duration.ofSeconds(30);
public static final Duration LEAVE_TIMEOUT = Duration.ofSeconds(2);
public static final Duration CALLBACK_TIMEOUT = Duration.ofSeconds(1);
+ public static final Duration SERVICE_DISCOVERY_TIMEOUT = Duration.ofSeconds(20);
private IntegrationTestUtils() {}
@@ -289,4 +297,106 @@
}
return false;
}
+
+ /** Return the first discovered service of {@code serviceType}. */
+ public static NsdServiceInfo discoverService(NsdManager nsdManager, String serviceType)
+ throws Exception {
+ CompletableFuture<NsdServiceInfo> serviceInfoFuture = new CompletableFuture<>();
+ NsdManager.DiscoveryListener listener =
+ new DefaultDiscoveryListener() {
+ @Override
+ public void onServiceFound(NsdServiceInfo serviceInfo) {
+ serviceInfoFuture.complete(serviceInfo);
+ }
+ };
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
+ try {
+ serviceInfoFuture.get(SERVICE_DISCOVERY_TIMEOUT.toMillis(), MILLISECONDS);
+ } finally {
+ nsdManager.stopServiceDiscovery(listener);
+ }
+
+ return serviceInfoFuture.get();
+ }
+
+ /**
+ * Returns the {@link NsdServiceInfo} when a service instance of {@code serviceType} gets lost.
+ */
+ public static NsdManager.DiscoveryListener discoverForServiceLost(
+ NsdManager nsdManager,
+ String serviceType,
+ CompletableFuture<NsdServiceInfo> serviceInfoFuture) {
+ NsdManager.DiscoveryListener listener =
+ new DefaultDiscoveryListener() {
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {
+ serviceInfoFuture.complete(serviceInfo);
+ }
+ };
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
+ return listener;
+ }
+
+ /** Resolves the service. */
+ public static NsdServiceInfo resolveService(NsdManager nsdManager, NsdServiceInfo serviceInfo)
+ throws Exception {
+ return resolveServiceUntil(nsdManager, serviceInfo, s -> true);
+ }
+
+ /** Returns the first resolved service that satisfies the {@code predicate}. */
+ public static NsdServiceInfo resolveServiceUntil(
+ NsdManager nsdManager, NsdServiceInfo serviceInfo, Predicate<NsdServiceInfo> predicate)
+ throws Exception {
+ CompletableFuture<NsdServiceInfo> resolvedServiceInfoFuture = new CompletableFuture<>();
+ NsdManager.ServiceInfoCallback callback =
+ new DefaultServiceInfoCallback() {
+ @Override
+ public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {
+ if (predicate.test(serviceInfo)) {
+ resolvedServiceInfoFuture.complete(serviceInfo);
+ }
+ }
+ };
+ nsdManager.registerServiceInfoCallback(serviceInfo, directExecutor(), callback);
+ try {
+ return resolvedServiceInfoFuture.get(
+ SERVICE_DISCOVERY_TIMEOUT.toMillis(), MILLISECONDS);
+ } finally {
+ nsdManager.unregisterServiceInfoCallback(callback);
+ }
+ }
+
+ private static class DefaultDiscoveryListener implements NsdManager.DiscoveryListener {
+ @Override
+ public void onStartDiscoveryFailed(String serviceType, int errorCode) {}
+
+ @Override
+ public void onStopDiscoveryFailed(String serviceType, int errorCode) {}
+
+ @Override
+ public void onDiscoveryStarted(String serviceType) {}
+
+ @Override
+ public void onDiscoveryStopped(String serviceType) {}
+
+ @Override
+ public void onServiceFound(NsdServiceInfo serviceInfo) {}
+
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {}
+ }
+
+ private static class DefaultServiceInfoCallback implements NsdManager.ServiceInfoCallback {
+ @Override
+ public void onServiceInfoCallbackRegistrationFailed(int errorCode) {}
+
+ @Override
+ public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {}
+
+ @Override
+ public void onServiceLost() {}
+
+ @Override
+ public void onServiceInfoCallbackUnregistered() {}
+ }
}
diff --git a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
index 8aea0a3..54e89b1 100644
--- a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.net.InetAddresses;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Handler;
@@ -42,6 +43,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.net.InetAddress;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -282,6 +285,189 @@
}
@Test
+ public void registerHost_nsdManagerSucceeds_serviceRegistrationSucceeds() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.registerHost(
+ "MyHost",
+ List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(),
+ actualRegistrationListenerCaptor.capture());
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+ mTestLooper.dispatchAll();
+
+ assertThat(actualServiceInfo.getServiceName()).isNull();
+ assertThat(actualServiceInfo.getServiceType()).isNull();
+ assertThat(actualServiceInfo.getSubtypes()).isEmpty();
+ assertThat(actualServiceInfo.getPort()).isEqualTo(0);
+ assertThat(actualServiceInfo.getAttributes()).isEmpty();
+ assertThat(actualServiceInfo.getHostname()).isEqualTo("MyHost");
+ assertThat(actualServiceInfo.getHostAddresses())
+ .isEqualTo(makeAddresses("2001:db8::1", "2001:db8::2", "2001:db8::3"));
+
+ verify(mRegistrationReceiver, times(1)).onSuccess();
+ }
+
+ @Test
+ public void registerHost_nsdManagerFails_serviceRegistrationFails() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.registerHost(
+ "MyHost",
+ List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(),
+ actualRegistrationListenerCaptor.capture());
+ mTestLooper.dispatchAll();
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onRegistrationFailed(actualServiceInfo, FAILURE_INTERNAL_ERROR);
+ mTestLooper.dispatchAll();
+
+ assertThat(actualServiceInfo.getServiceName()).isNull();
+ assertThat(actualServiceInfo.getServiceType()).isNull();
+ assertThat(actualServiceInfo.getSubtypes()).isEmpty();
+ assertThat(actualServiceInfo.getPort()).isEqualTo(0);
+ assertThat(actualServiceInfo.getAttributes()).isEmpty();
+ assertThat(actualServiceInfo.getHostname()).isEqualTo("MyHost");
+ assertThat(actualServiceInfo.getHostAddresses())
+ .isEqualTo(makeAddresses("2001:db8::1", "2001:db8::2", "2001:db8::3"));
+
+ verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
+ }
+
+ @Test
+ public void registerHost_nsdManagerThrows_serviceRegistrationFails() throws Exception {
+ prepareTest();
+
+ doThrow(new IllegalArgumentException("NsdManager fails"))
+ .when(mMockNsdManager)
+ .registerService(any(), anyInt(), any(Executor.class), any());
+
+ mNsdPublisher.registerHost(
+ "MyHost",
+ List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
+ }
+
+ @Test
+ public void unregisterHost_nsdManagerSucceeds_serviceUnregistrationSucceeds() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.registerHost(
+ "MyHost",
+ List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+ mNsdPublisher.unregister(mUnregistrationReceiver, 16 /* listenerId */);
+ mTestLooper.dispatchAll();
+ verify(mMockNsdManager, times(1)).unregisterService(actualRegistrationListener);
+
+ actualRegistrationListener.onServiceUnregistered(actualServiceInfo);
+ mTestLooper.dispatchAll();
+ verify(mUnregistrationReceiver, times(1)).onSuccess();
+ }
+
+ @Test
+ public void unregisterHost_nsdManagerFails_serviceUnregistrationFails() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.registerHost(
+ "MyHost",
+ List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+ mNsdPublisher.unregister(mUnregistrationReceiver, 16 /* listenerId */);
+ mTestLooper.dispatchAll();
+ verify(mMockNsdManager, times(1)).unregisterService(actualRegistrationListener);
+
+ actualRegistrationListener.onUnregistrationFailed(
+ actualServiceInfo, FAILURE_INTERNAL_ERROR);
+ mTestLooper.dispatchAll();
+ verify(mUnregistrationReceiver, times(1)).onError(0);
+ }
+
+ @Test
public void onOtDaemonDied_unregisterAll() {
prepareTest();
@@ -336,11 +522,30 @@
actualRegistrationListenerCaptor.getAllValues().get(1);
actualListener2.onServiceRegistered(actualServiceInfoCaptor.getValue());
+ mNsdPublisher.registerHost(
+ "Myhost",
+ List.of("2001:db8::1", "2001:db8::2", "2001:db8::3"),
+ mRegistrationReceiver,
+ 18 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ verify(mMockNsdManager, times(3))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+ NsdManager.RegistrationListener actualListener3 =
+ actualRegistrationListenerCaptor.getAllValues().get(1);
+ actualListener3.onServiceRegistered(actualServiceInfoCaptor.getValue());
+
mNsdPublisher.onOtDaemonDied();
mTestLooper.dispatchAll();
verify(mMockNsdManager, times(1)).unregisterService(actualListener1);
verify(mMockNsdManager, times(1)).unregisterService(actualListener2);
+ verify(mMockNsdManager, times(1)).unregisterService(actualListener3);
}
private static DnsTxtAttribute makeTxtAttribute(String name, List<Integer> value) {
@@ -356,6 +561,15 @@
return txtAttribute;
}
+ private static List<InetAddress> makeAddresses(String... addressStrings) {
+ List<InetAddress> addresses = new ArrayList<>();
+
+ for (String addressString : addressStrings) {
+ addresses.add(InetAddresses.parseNumericAddress(addressString));
+ }
+ return addresses;
+ }
+
// @Before and @Test run in different threads. NsdPublisher requires the jobs are run on the
// thread looper, so TestLooper needs to be created inside each test case to install the
// correct looper.