Merge "Declare MANAGE_EXTERNAL_STORAGE for test apps." into rvc-dev
diff --git a/tests/cts/hostside/AndroidTest.xml b/tests/cts/hostside/AndroidTest.xml
index 7cc0dd1..b7fefaf 100644
--- a/tests/cts/hostside/AndroidTest.xml
+++ b/tests/cts/hostside/AndroidTest.xml
@@ -20,6 +20,7 @@
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.cts.net.NetworkPolicyTestsPreparer" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 69dd2ad..1db0417 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -30,6 +30,7 @@
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -62,13 +63,10 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
/**
* Superclass for tests related to background network restrictions.
*/
-@RunWith(AndroidJUnit4.class)
+@RunWith(NetworkPolicyTestRunner.class)
public abstract class AbstractRestrictBackgroundNetworkTestCase {
public static final String TAG = "RestrictBackgroundNetworkTests";
@@ -129,16 +127,13 @@
protected Context mContext;
protected Instrumentation mInstrumentation;
protected ConnectivityManager mCm;
- protected WifiManager mWfm;
protected int mUid;
private int mMyUid;
private MyServiceClient mServiceClient;
private String mDeviceIdleConstantsSetting;
- private boolean mIsLocationOn;
@Rule
- public final RuleChain mRuleChain = RuleChain.outerRule(new DumpOnFailureRule())
- .around(new RequiredPropertiesRule())
+ public final RuleChain mRuleChain = RuleChain.outerRule(new RequiredPropertiesRule())
.around(new MeterednessConfigurationRule());
protected void setUp() throws Exception {
@@ -148,16 +143,11 @@
mInstrumentation = getInstrumentation();
mContext = getContext();
mCm = getConnectivityManager();
- mWfm = getWifiManager();
mUid = getUid(TEST_APP2_PKG);
mMyUid = getUid(mContext.getPackageName());
mServiceClient = new MyServiceClient(mContext);
mServiceClient.bind();
mDeviceIdleConstantsSetting = "device_idle_constants";
- mIsLocationOn = isLocationOn();
- if (!mIsLocationOn) {
- enableLocation();
- }
executeShellCommand("cmd netpolicy start-watching " + mUid);
setAppIdle(false);
@@ -168,33 +158,9 @@
protected void tearDown() throws Exception {
executeShellCommand("cmd netpolicy stop-watching");
- if (!mIsLocationOn) {
- disableLocation();
- }
mServiceClient.unbind();
}
- private void enableLocation() throws Exception {
- Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.LOCATION_MODE,
- Settings.Secure.LOCATION_MODE_SENSORS_ONLY);
- assertEquals(Settings.Secure.LOCATION_MODE_SENSORS_ONLY,
- Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.LOCATION_MODE));
- }
-
- private void disableLocation() throws Exception {
- Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.LOCATION_MODE,
- Settings.Secure.LOCATION_MODE_OFF);
- assertEquals(Settings.Secure.LOCATION_MODE_OFF,
- Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.LOCATION_MODE));
- }
-
- private boolean isLocationOn() throws Exception {
- return Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.LOCATION_MODE) != Settings.Secure.LOCATION_MODE_OFF;
- }
-
protected int getUid(String packageName) throws Exception {
return mContext.getPackageManager().getPackageUid(packageName, 0);
}
@@ -212,7 +178,9 @@
do {
attempts++;
count = getNumberBroadcastsReceived(receiverName, ACTION_RESTRICT_BACKGROUND_CHANGED);
- if (count >= expectedCount) {
+ assertFalse("Expected count " + expectedCount + " but actual is " + count,
+ count > expectedCount);
+ if (count == expectedCount) {
break;
}
Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after "
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestRunner.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestRunner.java
new file mode 100644
index 0000000..f340907
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestRunner.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.cts.net.hostside;
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+
+import org.junit.rules.RunRules;
+import org.junit.rules.TestRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+import java.util.List;
+
+/**
+ * Custom runner to allow dumping logs after a test failure before the @After methods get to run.
+ */
+public class NetworkPolicyTestRunner extends AndroidJUnit4ClassRunner {
+ private TestRule mDumpOnFailureRule = new DumpOnFailureRule();
+
+ public NetworkPolicyTestRunner(Class<?> klass) throws InitializationError {
+ super(klass);
+ }
+
+ @Override
+ public Statement methodInvoker(FrameworkMethod method, Object test) {
+ return new RunRules(super.methodInvoker(method, test), List.of(mDumpOnFailureRule),
+ describeChild(method));
+ }
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index ca2864c..3807d79 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -27,17 +27,20 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.content.Context;
+import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.wifi.WifiManager;
+import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -113,6 +116,20 @@
return am.isLowRamDevice();
}
+ public static boolean isLocationEnabled() {
+ final LocationManager lm = (LocationManager) getContext().getSystemService(
+ Context.LOCATION_SERVICE);
+ return lm.isLocationEnabled();
+ }
+
+ public static void setLocationEnabled(boolean enabled) {
+ final LocationManager lm = (LocationManager) getContext().getSystemService(
+ Context.LOCATION_SERVICE);
+ lm.setLocationEnabledForUser(enabled, Process.myUserHandle());
+ assertEquals("Couldn't change location enabled state", lm.isLocationEnabled(), enabled);
+ Log.d(TAG, "Changed location enabled state to " + enabled);
+ }
+
public static boolean isActiveNetworkMetered(boolean metered) {
return getConnectivityManager().isActiveNetworkMetered() == metered;
}
@@ -128,9 +145,21 @@
if (isActiveNetworkMetered(metered)) {
return null;
}
- final String ssid = unquoteSSID(getWifiManager().getConnectionInfo().getSSID());
- setWifiMeteredStatus(ssid, metered);
- return Pair.create(ssid, !metered);
+ final boolean isLocationEnabled = isLocationEnabled();
+ try {
+ if (!isLocationEnabled) {
+ setLocationEnabled(true);
+ }
+ final String ssid = unquoteSSID(getWifiManager().getConnectionInfo().getSSID());
+ assertNotEquals(WifiManager.UNKNOWN_SSID, ssid);
+ setWifiMeteredStatus(ssid, metered);
+ return Pair.create(ssid, !metered);
+ } finally {
+ // Reset the location enabled state
+ if (!isLocationEnabled) {
+ setLocationEnabled(false);
+ }
+ }
}
public static void resetMeteredNetwork(String ssid, boolean metered) throws Exception {
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 76bb27e..46fae33 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -47,6 +47,7 @@
"mockwebserver",
"junit",
"junit-params",
+ "libnanohttpd",
"truth-prebuilt",
],
diff --git a/tests/cts/net/AndroidManifest.xml b/tests/cts/net/AndroidManifest.xml
index c2b3bf7..baf914f 100644
--- a/tests/cts/net/AndroidManifest.xml
+++ b/tests/cts/net/AndroidManifest.xml
@@ -35,6 +35,11 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
+ <!-- This test also uses signature permissions through adopting the shell identity.
+ The permissions acquired that way include (probably not exhaustive) :
+ android.permission.MANAGE_TEST_NETWORKS
+ -->
+
<application android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner" />
<uses-library android:name="org.apache.http.legacy" android:required="false" />
diff --git a/tests/cts/net/ipsec/Android.bp b/tests/cts/net/ipsec/Android.bp
new file mode 100644
index 0000000..124e93c
--- /dev/null
+++ b/tests/cts/net/ipsec/Android.bp
@@ -0,0 +1,48 @@
+// Copyright (C) 2020 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.
+
+android_test {
+ name: "CtsIkeTestCases",
+ defaults: ["cts_defaults"],
+
+ // Include both the 32 and 64 bit versions
+ compile_multilib: "both",
+
+ libs: [
+ "android.net.ipsec.ike.stubs.system",
+ "android.test.base.stubs",
+ ],
+
+ srcs: [
+ "src/**/*.java",
+ ":ike-test-utils",
+ ],
+
+ static_libs: [
+ "androidx.test.ext.junit",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "net-tests-utils",
+ ],
+
+ platform_apis: true,
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "mts",
+ "vts",
+ "general-tests",
+ ],
+}
diff --git a/tests/cts/net/ipsec/AndroidManifest.xml b/tests/cts/net/ipsec/AndroidManifest.xml
new file mode 100644
index 0000000..de7d23c
--- /dev/null
+++ b/tests/cts/net/ipsec/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.net.ipsec.cts"
+ android:targetSandboxVersion="2">
+
+ <!--Allow tests to call ConnectivityManager#getActiveNetwork()-->
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <!--Allow tests to create socket -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+
+ <application android:label="CtsIkeTestCases">
+ <uses-library android:name="android.test.runner" />
+ <uses-library android:name="android.net.ipsec.ike" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.net.ipsec.cts"
+ android:label="CTS tests of android.net.ipsec">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
diff --git a/tests/cts/net/ipsec/AndroidTest.xml b/tests/cts/net/ipsec/AndroidTest.xml
new file mode 100644
index 0000000..09e5c93
--- /dev/null
+++ b/tests/cts/net/ipsec/AndroidTest.xml
@@ -0,0 +1,30 @@
+<!-- Copyright (C) 2020 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.
+-->
+<configuration description="Config for CTS IKE test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="networking" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="not-shardable" value="true" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsIkeTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.net.ipsec.cts" />
+ <option name="hidden-api-checks" value="false" />
+ </test>
+</configuration>
diff --git a/tests/cts/net/ipsec/assets/key/client-a-private-key.key b/tests/cts/net/ipsec/assets/key/client-a-private-key.key
new file mode 100644
index 0000000..22736e9
--- /dev/null
+++ b/tests/cts/net/ipsec/assets/key/client-a-private-key.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCv3CvrCGokJSWL
+8ufg6u9LCW4EezztbktqpC0T+1m98+Ujb8/eJ0L2UaxZ9QBSBAqXxEoeZFBeoCXu
+7ezUd5qUPfIhKLAkQTAyU/KgfhHh4i+MJK5ghPbGDE8r2gKUXOkM6M5//ZCpmu0K
+Y/9uQL6D5bkxEaoWegEO+wSXm+hTTgKDtQKHvRibgdcZkcY0cA9JsLrC/nIkP+7i
+pbBT+VTuV6gAnKIV0nq8zvI3A/Z3nAb5Gt0g3qaqs59StDT0QtuXzJkuZEo3XSrS
+jon+8NjSNzqVbJj95B7+uiH+91VEbMtJYFz2MipKvJQDK7Zlxke7LxRj2xJfksJK
+a92/ncxfAgMBAAECggEAQztaMvW5lm35J8LKsWs/5qEJRX9T8LWs8W0oqq36Riub
+G2wgvR6ndAIPcSjAYZqX7iOl7m6NZ0+0kN63HxdGqovwKIskpAekBGmhpYftED1n
+zh0r6UyMB3UnQ22KdOv8UOokIDxxdNX8728BdUYdT9Ggdkj5jLRB+VcwD0IUlNvo
+zzTpURV9HEd87uiLqd4AAHXSI0lIHI5U43z24HI/J6/YbYHT3Rlh6CIa/LuwO6vL
+gFkgqg0/oy6yJtjrHtzNVA67F0UaH62hR4YFgbC0d955SJnDidWOv/0j2DMpfdCc
+9kFAcPwUSyykvUSLnGIKWSG4D+6gzIeAeUx4oO7kMQKBgQDVNRkX8AGTHyLg+NXf
+spUWWcodwVioXl30Q7h6+4bt8OI61UbhQ7wX61wvJ1cySpa2KOYa2UdagQVhGhhL
+ADu363R77uXF/jZgzVfmjjyJ2nfDqRgHWRTlSkuq/jCOQCz7VIPHRZg5WL/9D4ms
+TAqMjpzqeMfFZI+w4/+xpcJIuQKBgQDTKBy+ZuerWrVT9icWKvLU58o5EVj/2yFy
+GJvKm+wRAAX2WzjNnR4HVd4DmMREVz1BPYby0j5gqjvtDsxYYu39+NT7JvMioLLK
+QPj+7k5geYgNqVgCxB1vP89RhY2X1RLrN9sTXOodgFPeXOQWNYITkGp3eQpx4nTJ
++K/al3oB1wKBgAjnc8nVIyuyxDEjE0OJYMKTM2a0uXAmqMPXxC+Wq5bqVXhhidlE
+i+lv0eTCPtkB1nN7F8kNQ/aaps/cWCFhvBy9P5shagUvzbOTP9WIIS0cq53HRRKh
+fMbqqGhWv05hjb9dUzeSR341n6cA7B3++v3Nwu3j52vt/DZF/1q68nc5AoGAS0SU
+ImbKE/GsizZGLoe2sZ/CHN+LKwCwhlwxRGKaHmE0vuE7eUeVSaYZEo0lAPtb8WJ+
+NRYueASWgeTxgFwbW5mUScZTirdfo+rPFwhZVdhcYApKPgosN9i2DOgfVcz1BnWN
+mPRY25U/0BaqkyQVruWeneG+kGPZn5kPDktKiVcCgYEAkzwU9vCGhm7ZVALvx/zR
+wARz2zsL9ImBc0P4DK1ld8g90FEnHrEgeI9JEwz0zFHOCMLwlk7kG0Xev7vfjZ7G
+xSqtQYOH33Qp6rtBOgdt8hSyDFvakvDl6bqhAw52gelO3MTpAB1+ZsfZ5gFx13Jf
+idNFcaIrC52PtZIH7QCzdDY=
+-----END PRIVATE KEY-----
\ No newline at end of file
diff --git a/tests/cts/net/ipsec/assets/pem/client-a-end-cert.pem b/tests/cts/net/ipsec/assets/pem/client-a-end-cert.pem
new file mode 100644
index 0000000..e82da85
--- /dev/null
+++ b/tests/cts/net/ipsec/assets/pem/client-a-end-cert.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDaDCCAlCgAwIBAgIIcorRI3n29E4wDQYJKoZIhvcNAQELBQAwQTELMAkGA1UE
+BhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxIDAeBgNVBAMTF3R3by5jYS50ZXN0LmFu
+ZHJvaWQubmV0MB4XDTIwMDQxNDA1MDM0OVoXDTIzMDQxNDA1MDM0OVowRTELMAkG
+A1UEBhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxJDAiBgNVBAMTG2NsaWVudC50ZXN0
+LmlrZS5hbmRyb2lkLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AK/cK+sIaiQlJYvy5+Dq70sJbgR7PO1uS2qkLRP7Wb3z5SNvz94nQvZRrFn1AFIE
+CpfESh5kUF6gJe7t7NR3mpQ98iEosCRBMDJT8qB+EeHiL4wkrmCE9sYMTyvaApRc
+6Qzozn/9kKma7Qpj/25AvoPluTERqhZ6AQ77BJeb6FNOAoO1Aoe9GJuB1xmRxjRw
+D0mwusL+ciQ/7uKlsFP5VO5XqACcohXSerzO8jcD9necBvka3SDepqqzn1K0NPRC
+25fMmS5kSjddKtKOif7w2NI3OpVsmP3kHv66If73VURsy0lgXPYyKkq8lAMrtmXG
+R7svFGPbEl+Swkpr3b+dzF8CAwEAAaNgMF4wHwYDVR0jBBgwFoAUcqSu1uRYT/DL
+bLoDNUz38nGvCKQwJgYDVR0RBB8wHYIbY2xpZW50LnRlc3QuaWtlLmFuZHJvaWQu
+bmV0MBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQCa53tK
+I9RM9/MutZ5KNG2Gfs2cqaPyv8ZRhs90HDWZhkFVu7prywJAxOd2hxxHPsvgurio
+4bKAxnT4EXevgz5YoCbj2TPIL9TdFYh59zZ97XXMxk+SRdypgF70M6ETqKPs3hDP
+ZRMMoHvvYaqaPvp4StSBX9A44gSyjHxVYJkrjDZ0uffKg5lFL5IPvqfdmSRSpGab
+SyGTP4OLTy0QiNV3pBsJGdl0h5BzuTPR9OTl4xgeqqBQy2bDjmfJBuiYyCSCkPi7
+T3ohDYCymhuSkuktHPNG1aKllUJaw0tuZuNydlgdAveXPYfM36uvK0sfd9qr9pAy
+rmkYV2MAWguFeckh
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/tests/cts/net/ipsec/assets/pem/client-a-intermediate-ca-one.pem b/tests/cts/net/ipsec/assets/pem/client-a-intermediate-ca-one.pem
new file mode 100644
index 0000000..707e575
--- /dev/null
+++ b/tests/cts/net/ipsec/assets/pem/client-a-intermediate-ca-one.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDaDCCAlCgAwIBAgIIIbjMyRn2770wDQYJKoZIhvcNAQELBQAwQjELMAkGA1UE
+BhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxITAfBgNVBAMTGHJvb3QuY2EudGVzdC5h
+bmRyb2lkLm5ldDAeFw0xOTA5MzAxODQzMThaFw0yNDA5MjgxODQzMThaMEExCzAJ
+BgNVBAYTAlVTMRAwDgYDVQQKEwdBbmRyb2lkMSAwHgYDVQQDExdvbmUuY2EudGVz
+dC5hbmRyb2lkLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKNN
+sRr5Z30rAEw2jrAh/BIekbEy/MvOucAr1w0lxH71p+ybRBx5Bj7G07UGXbL659gm
+meMV6nabY4HjQXNMq22POiJBZj+U+rw34br6waljBttxCmmJac1VvgqNsSspXjRy
+NbiVQdFjyKSX0NOPcEkwANk15mZbOgJBaYYc8jQCY2G/p8eARVBTLJCy8LEwEU6j
+XRv/4eYST79qpBFc7gQQj2FLmh9oppDIvcIVBHwtd1tBoVuehRSud1o8vQRkl/HJ
+Mrwp24nO5YYhmVNSFRtBpmWMSu1KknFUwkOebINUNsKXXHebVa7cP4XIQUL8mRT3
+5X9rFJFSQJE01S3NjNMCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
+Af8EBAMCAQYwHQYDVR0OBBYEFHK3FIm7g8dxEIwK9zMAO8EWhRYxMB8GA1UdIwQY
+MBaAFEmfqEeF14Nj91ekIpR+sVhCEoAaMA0GCSqGSIb3DQEBCwUAA4IBAQAeMlXT
+TnxZo8oz0204gKZ63RzlgDpJ7SqA3qFG+pV+TiqGfSuVkXuIdOskjxJnA9VxUzrr
+LdMTCn5e0FK6wCYjZ2GT/CD7oD3vSMkzGbLGNcNJhhDHUq8BOLPkPzz/rwQFPBSb
+zr6hsiVXphEt/psGoN7Eu9blPeQaIwMfWnaufAwF664S/3dmCRbNMWSam1qzzz8q
+jr0cDOIMa//ZIAcM16cvoBK6pFGnUmuoJYYRtfpY5MmfCWz0sCJxENIX/lxyhd7N
+FdRALA1ZP3E//Tn2vQoeFjbKaAba527RE26HgHJ9zZDo1nn8J8J/YwYRJdBWM/3S
+LYebNiMtcyB5nIkj
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/tests/cts/net/ipsec/assets/pem/client-a-intermediate-ca-two.pem b/tests/cts/net/ipsec/assets/pem/client-a-intermediate-ca-two.pem
new file mode 100644
index 0000000..39808f8
--- /dev/null
+++ b/tests/cts/net/ipsec/assets/pem/client-a-intermediate-ca-two.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAk+gAwIBAgIIKWCREnNCs+wwDQYJKoZIhvcNAQELBQAwQTELMAkGA1UE
+BhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxIDAeBgNVBAMTF29uZS5jYS50ZXN0LmFu
+ZHJvaWQubmV0MB4XDTE5MDkzMDE4NDQwMloXDTI0MDkyODE4NDQwMlowQTELMAkG
+A1UEBhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxIDAeBgNVBAMTF3R3by5jYS50ZXN0
+LmFuZHJvaWQubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxLUa
+RqkYl2m7lUmMnkooqO0DNNY1aN9r7mJc3ndYn5gjkpb3yLgOYPDNLcQerV6uWk/u
+qKudNHed2dInGonl3oxwwv7++6oUvvtrSWLDZlRg16GsdIE1Y98DSMQWkSxevYy9
+Nh6FGTdlBFQVMpiMa8qHEkrOyKsy85yCW1sgzlpGTIBwbDAqYtwe3rgbwyHwUtfy
+0EU++DBcR4ll/pDqB0OQtW5E3AOq2GH1iaGeFLKSUQ5KAbdI8y4/b8IkSDffvxcc
+kXig7S54aLrNlL/ZjQ+H4Chgjj2A5wMucd81+Fb60Udej73ICL9PpMPnXQ1+BVYd
+MJ/txjLNmrOJG9yEHQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
+/wQEAwIBBjAdBgNVHQ4EFgQUcqSu1uRYT/DLbLoDNUz38nGvCKQwHwYDVR0jBBgw
+FoAUcrcUibuDx3EQjAr3MwA7wRaFFjEwDQYJKoZIhvcNAQELBQADggEBADY461GT
+Rw0dGnD07xaGJcI0i0pV+WnGSrl1s1PAIdMYihJAqYnh10fXbFXLm2WMWVmv/pxs
+FI/xDJno+pd4mCa/sIhm63ar/Nv+lFQmcpIlvSlKnhhV4SLNBeqbVhPBGTCHfrG4
+aIyCwm1KJsnkWbf03crhSskR/2CXIjX6lcAy7K3fE2u1ELpAdH0kMJR7VXkLFLUm
+gqe9YCluR0weMpe2sCaOGzdVzQSmMMCzGP5cxeFR5U6K40kMOpiW11JNmQ06xI/m
+YVkMNwoiV/ITT0/C/g9FxJmkO0mVSLEqxaLS/hNiQNDlroVM0rbxhzviXLI3R3AO
+50VvlOQYGxWed/I=
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/tests/cts/net/ipsec/assets/pem/server-a-self-signed-ca.pem b/tests/cts/net/ipsec/assets/pem/server-a-self-signed-ca.pem
new file mode 100644
index 0000000..972fd55
--- /dev/null
+++ b/tests/cts/net/ipsec/assets/pem/server-a-self-signed-ca.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDSDCCAjCgAwIBAgIITJQJ6HC1rjwwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UE
+BhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxITAfBgNVBAMTGHJvb3QuY2EudGVzdC5h
+bmRyb2lkLm5ldDAeFw0xOTA5MzAxNzU1NTJaFw0yOTA5MjcxNzU1NTJaMEIxCzAJ
+BgNVBAYTAlVTMRAwDgYDVQQKEwdBbmRyb2lkMSEwHwYDVQQDExhyb290LmNhLnRl
+c3QuYW5kcm9pZC5uZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCT
+q3hGF+JvLaB1xW7KGKmaxiQ7BxX2Sn7cbp7ggoVYXsFlBUuPPv3+Vg5PfPCPhsJ8
+/7w4HyKo3uc/vHs5HpQ7rSd9blhAkfmJci2ULLq73FB8Mix4CzPwMx29RrN1X9bU
+z4G0vJMczIBGxbZ0uw7n8bKcXBV7AIeax+J8lseEZ3k8iSuBkUJqGIpPFKTqByFZ
+A1Lvt47xkON5SZh6c/Oe+o6291wXaCOJUSAKv6PAWZkq9HeD2fqKA/ck9dBaz1M3
+YvzQ9V/7so3/dECjAfKia388h1I6XSGNUM+d5hpxMXpAFgG42eUXHpJ10OjDvSwd
+7ZSC91/kRQewUomEKBK1AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
+AQH/BAQDAgEGMB0GA1UdDgQWBBRJn6hHhdeDY/dXpCKUfrFYQhKAGjANBgkqhkiG
+9w0BAQsFAAOCAQEAig/94aGfHBhZuvbbhwAK4rUNpizmR567u0ZJ+QUEKyAlo9lT
+ZWYHSm7qTAZYvPEjzTQIptnAlxCHePXh3Cfwgo+r82lhG2rcdI03iRyvHWjM8gyk
+BXCJTi0Q08JHHpTP6GnAqpz58qEIFkk8P766zNXdhYrGPOydF+p7MFcb1Zv1gum3
+zmRLt0XUAMfjPUv1Bl8kTKFxH5lkMBLR1E0jnoJoTTfgRPrf9CuFSoh48n7YhoBT
+KV75xZY8b8+SuB0v6BvQmkpKZGoxBjuVsShyG7q1+4JTAtwhiP7BlkDvVkaBEi7t
+WIMFp2r2ZDisHgastNaeYFyzHYz9g1FCCrHQ4w==
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/tests/cts/net/ipsec/src/android/net/eap/cts/EapSessionConfigTest.java b/tests/cts/net/ipsec/src/android/net/eap/cts/EapSessionConfigTest.java
new file mode 100644
index 0000000..c24379d
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/eap/cts/EapSessionConfigTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 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.eap.cts;
+
+import static android.telephony.TelephonyManager.APPTYPE_USIM;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.eap.EapSessionConfig;
+import android.net.eap.EapSessionConfig.EapAkaConfig;
+import android.net.eap.EapSessionConfig.EapAkaPrimeConfig;
+import android.net.eap.EapSessionConfig.EapMsChapV2Config;
+import android.net.eap.EapSessionConfig.EapSimConfig;
+import android.net.eap.EapSessionConfig.EapUiccConfig;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class EapSessionConfigTest {
+ // These constants are IANA-defined values and are copies of hidden constants in
+ // frameworks/opt/net/ike/src/java/com/android/internal/net/eap/message/EapData.java.
+ private static final int EAP_TYPE_SIM = 18;
+ private static final int EAP_TYPE_AKA = 23;
+ private static final int EAP_TYPE_MSCHAP_V2 = 26;
+ private static final int EAP_TYPE_AKA_PRIME = 50;
+
+ private static final int SUB_ID = 1;
+ private static final byte[] EAP_IDENTITY = "test@android.net".getBytes();
+ private static final String NETWORK_NAME = "android.net";
+ private static final String EAP_MSCHAPV2_USERNAME = "username";
+ private static final String EAP_MSCHAPV2_PASSWORD = "password";
+
+ @Test
+ public void testBuildWithAllEapMethods() {
+ EapSessionConfig result =
+ new EapSessionConfig.Builder()
+ .setEapIdentity(EAP_IDENTITY)
+ .setEapSimConfig(SUB_ID, APPTYPE_USIM)
+ .setEapAkaConfig(SUB_ID, APPTYPE_USIM)
+ .setEapAkaPrimeConfig(
+ SUB_ID,
+ APPTYPE_USIM,
+ NETWORK_NAME,
+ true /* allowMismatchedNetworkNames */)
+ .setEapMsChapV2Config(EAP_MSCHAPV2_USERNAME, EAP_MSCHAPV2_PASSWORD)
+ .build();
+
+ assertArrayEquals(EAP_IDENTITY, result.getEapIdentity());
+
+ EapSimConfig eapSimConfig = result.getEapSimConfig();
+ assertNotNull(eapSimConfig);
+ assertEquals(EAP_TYPE_SIM, eapSimConfig.getMethodType());
+ verifyEapUiccConfigCommon(eapSimConfig);
+
+ EapAkaConfig eapAkaConfig = result.getEapAkaConfig();
+ assertNotNull(eapAkaConfig);
+ assertEquals(EAP_TYPE_AKA, eapAkaConfig.getMethodType());
+ verifyEapUiccConfigCommon(eapAkaConfig);
+
+ EapAkaPrimeConfig eapAkaPrimeConfig = result.getEapAkaPrimeConfig();
+ assertNotNull(eapAkaPrimeConfig);
+ assertEquals(EAP_TYPE_AKA_PRIME, eapAkaPrimeConfig.getMethodType());
+ assertEquals(NETWORK_NAME, eapAkaPrimeConfig.getNetworkName());
+ assertTrue(NETWORK_NAME, eapAkaPrimeConfig.allowsMismatchedNetworkNames());
+ verifyEapUiccConfigCommon(eapAkaPrimeConfig);
+
+ EapMsChapV2Config eapMsChapV2Config = result.getEapMsChapV2onfig();
+ assertNotNull(eapMsChapV2Config);
+ assertEquals(EAP_TYPE_MSCHAP_V2, eapMsChapV2Config.getMethodType());
+ assertEquals(EAP_MSCHAPV2_USERNAME, eapMsChapV2Config.getUsername());
+ assertEquals(EAP_MSCHAPV2_PASSWORD, eapMsChapV2Config.getPassword());
+ }
+
+ private void verifyEapUiccConfigCommon(EapUiccConfig config) {
+ assertEquals(SUB_ID, config.getSubId());
+ assertEquals(APPTYPE_USIM, config.getAppType());
+ }
+}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/ChildSessionParamsTest.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/ChildSessionParamsTest.java
new file mode 100644
index 0000000..7fb1b6d
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/ChildSessionParamsTest.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2020 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.ipsec.ike.cts;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.LinkAddress;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.ChildSessionParams;
+import android.net.ipsec.ike.TransportModeChildSessionParams;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Address;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DhcpServer;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DnsServer;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Netmask;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6Address;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6DnsServer;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class ChildSessionParamsTest extends IkeTestBase {
+ private static final int HARD_LIFETIME_SECONDS = (int) TimeUnit.HOURS.toSeconds(3L);
+ private static final int SOFT_LIFETIME_SECONDS = (int) TimeUnit.HOURS.toSeconds(1L);
+
+ // Random proposal. Content doesn't matter
+ private final ChildSaProposal mSaProposal =
+ SaProposalTest.buildChildSaProposalWithCombinedModeCipher();
+
+ private void verifyTunnelModeChildParamsWithDefaultValues(ChildSessionParams childParams) {
+ assertTrue(childParams instanceof TunnelModeChildSessionParams);
+ verifyChildParamsWithDefaultValues(childParams);
+ }
+
+ private void verifyTunnelModeChildParamsWithCustomizedValues(ChildSessionParams childParams) {
+ assertTrue(childParams instanceof TunnelModeChildSessionParams);
+ verifyChildParamsWithCustomizedValues(childParams);
+ }
+
+ private void verifyTransportModeChildParamsWithDefaultValues(ChildSessionParams childParams) {
+ assertTrue(childParams instanceof TransportModeChildSessionParams);
+ verifyChildParamsWithDefaultValues(childParams);
+ }
+
+ private void verifyTransportModeChildParamsWithCustomizedValues(
+ ChildSessionParams childParams) {
+ assertTrue(childParams instanceof TransportModeChildSessionParams);
+ verifyChildParamsWithCustomizedValues(childParams);
+ }
+
+ private void verifyChildParamsWithDefaultValues(ChildSessionParams childParams) {
+ assertEquals(Arrays.asList(mSaProposal), childParams.getSaProposals());
+
+ // Do not do assertEquals to the default values to be avoid being a change-detector test
+ assertTrue(childParams.getHardLifetimeSeconds() > childParams.getSoftLifetimeSeconds());
+ assertTrue(childParams.getSoftLifetimeSeconds() > 0);
+
+ assertEquals(
+ Arrays.asList(DEFAULT_V4_TS, DEFAULT_V6_TS),
+ childParams.getInboundTrafficSelectors());
+ assertEquals(
+ Arrays.asList(DEFAULT_V4_TS, DEFAULT_V6_TS),
+ childParams.getOutboundTrafficSelectors());
+ }
+
+ private void verifyChildParamsWithCustomizedValues(ChildSessionParams childParams) {
+ assertEquals(Arrays.asList(mSaProposal), childParams.getSaProposals());
+
+ assertEquals(HARD_LIFETIME_SECONDS, childParams.getHardLifetimeSeconds());
+ assertEquals(SOFT_LIFETIME_SECONDS, childParams.getSoftLifetimeSeconds());
+
+ assertEquals(
+ Arrays.asList(INBOUND_V4_TS, INBOUND_V6_TS),
+ childParams.getInboundTrafficSelectors());
+ assertEquals(
+ Arrays.asList(OUTBOUND_V4_TS, OUTBOUND_V6_TS),
+ childParams.getOutboundTrafficSelectors());
+ }
+
+ @Test
+ public void testBuildTransportModeParamsWithDefaultValues() {
+ TransportModeChildSessionParams childParams =
+ new TransportModeChildSessionParams.Builder().addSaProposal(mSaProposal).build();
+
+ verifyTransportModeChildParamsWithDefaultValues(childParams);
+ }
+
+ @Test
+ public void testBuildTunnelModeParamsWithDefaultValues() {
+ TunnelModeChildSessionParams childParams =
+ new TunnelModeChildSessionParams.Builder().addSaProposal(mSaProposal).build();
+
+ verifyTunnelModeChildParamsWithDefaultValues(childParams);
+ assertTrue(childParams.getConfigurationRequests().isEmpty());
+ }
+
+ @Test
+ public void testBuildTransportModeParamsWithCustomizedValues() {
+ TransportModeChildSessionParams childParams =
+ new TransportModeChildSessionParams.Builder()
+ .addSaProposal(mSaProposal)
+ .setLifetimeSeconds(HARD_LIFETIME_SECONDS, SOFT_LIFETIME_SECONDS)
+ .addInboundTrafficSelectors(INBOUND_V4_TS)
+ .addInboundTrafficSelectors(INBOUND_V6_TS)
+ .addOutboundTrafficSelectors(OUTBOUND_V4_TS)
+ .addOutboundTrafficSelectors(OUTBOUND_V6_TS)
+ .build();
+
+ verifyTransportModeChildParamsWithCustomizedValues(childParams);
+ }
+
+ @Test
+ public void testBuildTunnelModeParamsWithCustomizedValues() {
+ TunnelModeChildSessionParams childParams =
+ new TunnelModeChildSessionParams.Builder()
+ .addSaProposal(mSaProposal)
+ .setLifetimeSeconds(HARD_LIFETIME_SECONDS, SOFT_LIFETIME_SECONDS)
+ .addInboundTrafficSelectors(INBOUND_V4_TS)
+ .addInboundTrafficSelectors(INBOUND_V6_TS)
+ .addOutboundTrafficSelectors(OUTBOUND_V4_TS)
+ .addOutboundTrafficSelectors(OUTBOUND_V6_TS)
+ .build();
+
+ verifyTunnelModeChildParamsWithCustomizedValues(childParams);
+ }
+
+ @Test
+ public void testBuildChildSessionParamsWithConfigReq() {
+ TunnelModeChildSessionParams childParams =
+ new TunnelModeChildSessionParams.Builder()
+ .addSaProposal(mSaProposal)
+ .addInternalAddressRequest(AF_INET)
+ .addInternalAddressRequest(AF_INET6)
+ .addInternalAddressRequest(AF_INET6)
+ .addInternalAddressRequest(IPV4_ADDRESS_REMOTE)
+ .addInternalAddressRequest(IPV6_ADDRESS_REMOTE, IP6_PREFIX_LEN)
+ .addInternalDnsServerRequest(AF_INET)
+ .addInternalDnsServerRequest(AF_INET6)
+ .addInternalDhcpServerRequest(AF_INET)
+ .addInternalDhcpServerRequest(AF_INET)
+ .build();
+
+ verifyTunnelModeChildParamsWithDefaultValues(childParams);
+
+ // Verify config request types and number of requests for each type
+ Map<Class<? extends TunnelModeChildConfigRequest>, Integer> expectedAttributeCounts =
+ new HashMap<>();
+ expectedAttributeCounts.put(ConfigRequestIpv4Address.class, 2);
+ expectedAttributeCounts.put(ConfigRequestIpv6Address.class, 3);
+ expectedAttributeCounts.put(ConfigRequestIpv4Netmask.class, 1);
+ expectedAttributeCounts.put(ConfigRequestIpv4DnsServer.class, 1);
+ expectedAttributeCounts.put(ConfigRequestIpv6DnsServer.class, 1);
+ expectedAttributeCounts.put(ConfigRequestIpv4DhcpServer.class, 2);
+ verifyConfigRequestTypes(expectedAttributeCounts, childParams.getConfigurationRequests());
+
+ // Verify specific IPv4 address request
+ Set<Inet4Address> expectedV4Addresses = new HashSet<>();
+ expectedV4Addresses.add(IPV4_ADDRESS_REMOTE);
+ verifySpecificV4AddrConfigReq(expectedV4Addresses, childParams);
+
+ // Verify specific IPv6 address request
+ Set<LinkAddress> expectedV6Addresses = new HashSet<>();
+ expectedV6Addresses.add(new LinkAddress(IPV6_ADDRESS_REMOTE, IP6_PREFIX_LEN));
+ verifySpecificV6AddrConfigReq(expectedV6Addresses, childParams);
+ }
+
+ protected void verifySpecificV4AddrConfigReq(
+ Set<Inet4Address> expectedAddresses, TunnelModeChildSessionParams childParams) {
+ for (TunnelModeChildConfigRequest req : childParams.getConfigurationRequests()) {
+ if (req instanceof ConfigRequestIpv4Address
+ && ((ConfigRequestIpv4Address) req).getAddress() != null) {
+ Inet4Address address = ((ConfigRequestIpv4Address) req).getAddress();
+
+ // Fail if expectedAddresses does not contain this address
+ assertTrue(expectedAddresses.remove(address));
+ }
+ }
+
+ // Fail if any expected address is not found in result
+ assertTrue(expectedAddresses.isEmpty());
+ }
+
+ protected void verifySpecificV6AddrConfigReq(
+ Set<LinkAddress> expectedAddresses, TunnelModeChildSessionParams childParams) {
+ for (TunnelModeChildConfigRequest req : childParams.getConfigurationRequests()) {
+ if (req instanceof ConfigRequestIpv6Address
+ && ((ConfigRequestIpv6Address) req).getAddress() != null) {
+ ConfigRequestIpv6Address ipv6AddrReq = (ConfigRequestIpv6Address) req;
+
+ // Fail if expectedAddresses does not contain this address
+ LinkAddress address =
+ new LinkAddress(ipv6AddrReq.getAddress(), ipv6AddrReq.getPrefixLength());
+ assertTrue(expectedAddresses.remove(address));
+ }
+ }
+
+ // Fail if any expected address is not found in result
+ assertTrue(expectedAddresses.isEmpty());
+ }
+}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeIdentificationTest.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeIdentificationTest.java
new file mode 100644
index 0000000..0317def
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeIdentificationTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 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.ipsec.ike.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.net.ipsec.ike.IkeDerAsn1DnIdentification;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeIpv6AddrIdentification;
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeRfc822AddrIdentification;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import javax.security.auth.x500.X500Principal;
+
+@RunWith(AndroidJUnit4.class)
+public final class IkeIdentificationTest extends IkeTestBase {
+ @Test
+ public void testIkeDerAsn1DnIdentification() throws Exception {
+ X500Principal asn1Dn = new X500Principal(LOCAL_ASN1_DN_STRING);
+
+ IkeDerAsn1DnIdentification ikeId = new IkeDerAsn1DnIdentification(asn1Dn);
+ assertEquals(asn1Dn, ikeId.derAsn1Dn);
+ }
+
+ @Test
+ public void testIkeFqdnIdentification() throws Exception {
+ IkeFqdnIdentification ikeId = new IkeFqdnIdentification(LOCAL_HOSTNAME);
+ assertEquals(LOCAL_HOSTNAME, ikeId.fqdn);
+ }
+
+ @Test
+ public void testIkeIpv4AddrIdentification() throws Exception {
+ IkeIpv4AddrIdentification ikeId = new IkeIpv4AddrIdentification(IPV4_ADDRESS_LOCAL);
+ assertEquals(IPV4_ADDRESS_LOCAL, ikeId.ipv4Address);
+ }
+
+ @Test
+ public void testIkeIpv6AddrIdentification() throws Exception {
+ IkeIpv6AddrIdentification ikeId = new IkeIpv6AddrIdentification(IPV6_ADDRESS_LOCAL);
+ assertEquals(IPV6_ADDRESS_LOCAL, ikeId.ipv6Address);
+ }
+
+ @Test
+ public void testIkeKeyIdIdentification() throws Exception {
+ IkeKeyIdIdentification ikeId = new IkeKeyIdIdentification(LOCAL_KEY_ID);
+ assertArrayEquals(LOCAL_KEY_ID, ikeId.keyId);
+ }
+
+ @Test
+ public void testIkeRfc822AddrIdentification() throws Exception {
+ IkeRfc822AddrIdentification ikeId = new IkeRfc822AddrIdentification(LOCAL_RFC822_NAME);
+ assertEquals(LOCAL_RFC822_NAME, ikeId.rfc822Name);
+ }
+}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionParamsTest.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionParamsTest.java
new file mode 100644
index 0000000..c767b78
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionParamsTest.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2020 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.ipsec.ike.cts;
+
+import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID;
+import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH;
+import static android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig;
+import static android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignLocalConfig;
+import static android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignRemoteConfig;
+import static android.net.ipsec.ike.IkeSessionParams.IkeAuthEapConfig;
+import static android.net.ipsec.ike.IkeSessionParams.IkeAuthPskConfig;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.telephony.TelephonyManager.APPTYPE_USIM;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.eap.EapSessionConfig;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeIdentification;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeSessionParams.ConfigRequestIpv4PcscfServer;
+import android.net.ipsec.ike.IkeSessionParams.ConfigRequestIpv6PcscfServer;
+import android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.net.ipsec.ike.testutils.CertUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public final class IkeSessionParamsTest extends IkeSessionTestBase {
+ private static final int HARD_LIFETIME_SECONDS = (int) TimeUnit.HOURS.toSeconds(20L);
+ private static final int SOFT_LIFETIME_SECONDS = (int) TimeUnit.HOURS.toSeconds(10L);
+ private static final int DPD_DELAY_SECONDS = (int) TimeUnit.MINUTES.toSeconds(10L);
+ private static final int[] RETRANS_TIMEOUT_MS_LIST = new int[] {500, 500, 500, 500, 500, 500};
+
+ private static final Map<Class<? extends IkeConfigRequest>, Integer> EXPECTED_REQ_COUNT =
+ new HashMap<>();
+ private static final HashSet<InetAddress> EXPECTED_PCSCF_SERVERS = new HashSet<>();
+
+ static {
+ EXPECTED_REQ_COUNT.put(ConfigRequestIpv4PcscfServer.class, 3);
+ EXPECTED_REQ_COUNT.put(ConfigRequestIpv6PcscfServer.class, 3);
+
+ EXPECTED_PCSCF_SERVERS.add(PCSCF_IPV4_ADDRESS_1);
+ EXPECTED_PCSCF_SERVERS.add(PCSCF_IPV4_ADDRESS_2);
+ EXPECTED_PCSCF_SERVERS.add(PCSCF_IPV6_ADDRESS_1);
+ EXPECTED_PCSCF_SERVERS.add(PCSCF_IPV6_ADDRESS_2);
+ }
+
+ // Arbitrary proposal and remote ID. Local ID is chosen to match the client end cert in the
+ // following CL
+ private static final IkeSaProposal SA_PROPOSAL =
+ SaProposalTest.buildIkeSaProposalWithNormalModeCipher();
+ private static final IkeIdentification LOCAL_ID = new IkeFqdnIdentification(LOCAL_HOSTNAME);
+ private static final IkeIdentification REMOTE_ID = new IkeFqdnIdentification(REMOTE_HOSTNAME);
+
+ private static final EapSessionConfig EAP_ALL_METHODS_CONFIG =
+ createEapOnlySafeMethodsBuilder()
+ .setEapMsChapV2Config(EAP_MSCHAPV2_USERNAME, EAP_MSCHAPV2_PASSWORD)
+ .build();
+ private static final EapSessionConfig EAP_ONLY_SAFE_METHODS_CONFIG =
+ createEapOnlySafeMethodsBuilder().build();
+
+ private X509Certificate mServerCaCert;
+ private X509Certificate mClientEndCert;
+ private X509Certificate mClientIntermediateCaCertOne;
+ private X509Certificate mClientIntermediateCaCertTwo;
+ private RSAPrivateKey mClientPrivateKey;
+
+ @Before
+ public void setUp() throws Exception {
+ // This address is never used except for setting up the test network
+ setUpTestNetwork(IPV4_ADDRESS_LOCAL);
+
+ mServerCaCert = CertUtils.createCertFromPemFile("server-a-self-signed-ca.pem");
+ mClientEndCert = CertUtils.createCertFromPemFile("client-a-end-cert.pem");
+ mClientIntermediateCaCertOne =
+ CertUtils.createCertFromPemFile("client-a-intermediate-ca-one.pem");
+ mClientIntermediateCaCertTwo =
+ CertUtils.createCertFromPemFile("client-a-intermediate-ca-two.pem");
+ mClientPrivateKey = CertUtils.createRsaPrivateKeyFromKeyFile("client-a-private-key.key");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ tearDownTestNetwork();
+ }
+
+ private static EapSessionConfig.Builder createEapOnlySafeMethodsBuilder() {
+ return new EapSessionConfig.Builder()
+ .setEapIdentity(EAP_IDENTITY)
+ .setEapSimConfig(SUB_ID, APPTYPE_USIM)
+ .setEapAkaConfig(SUB_ID, APPTYPE_USIM)
+ .setEapAkaPrimeConfig(
+ SUB_ID, APPTYPE_USIM, NETWORK_NAME, true /* allowMismatchedNetworkNames */);
+ }
+
+ /**
+ * Create a Builder that has minimum configurations to build an IkeSessionParams.
+ *
+ * <p>Authentication method is arbitrarily selected. Using other method (e.g. setAuthEap) also
+ * works.
+ */
+ private IkeSessionParams.Builder createIkeParamsBuilderMinimum() {
+ return new IkeSessionParams.Builder(sContext)
+ .setNetwork(mTunNetwork)
+ .setServerHostname(IPV4_ADDRESS_REMOTE.getHostAddress())
+ .addSaProposal(SA_PROPOSAL)
+ .setLocalIdentification(LOCAL_ID)
+ .setRemoteIdentification(REMOTE_ID)
+ .setAuthPsk(IKE_PSK);
+ }
+
+ /**
+ * Verify the minimum configurations to build an IkeSessionParams.
+ *
+ * @see #createIkeParamsBuilderMinimum
+ */
+ private void verifyIkeParamsMinimum(IkeSessionParams sessionParams) {
+ assertEquals(mTunNetwork, sessionParams.getNetwork());
+ assertEquals(IPV4_ADDRESS_REMOTE.getHostAddress(), sessionParams.getServerHostname());
+ assertEquals(Arrays.asList(SA_PROPOSAL), sessionParams.getSaProposals());
+ assertEquals(LOCAL_ID, sessionParams.getLocalIdentification());
+ assertEquals(REMOTE_ID, sessionParams.getRemoteIdentification());
+
+ IkeAuthConfig localConfig = sessionParams.getLocalAuthConfig();
+ assertTrue(localConfig instanceof IkeAuthPskConfig);
+ assertArrayEquals(IKE_PSK, ((IkeAuthPskConfig) localConfig).getPsk());
+ IkeAuthConfig remoteConfig = sessionParams.getRemoteAuthConfig();
+ assertTrue(remoteConfig instanceof IkeAuthPskConfig);
+ assertArrayEquals(IKE_PSK, ((IkeAuthPskConfig) remoteConfig).getPsk());
+ }
+
+ @Test
+ public void testBuildWithMinimumSet() throws Exception {
+ IkeSessionParams sessionParams = createIkeParamsBuilderMinimum().build();
+
+ verifyIkeParamsMinimum(sessionParams);
+
+ // Verify default values that do not need explicit configuration. Do not do assertEquals
+ // to be avoid being a change-detector test
+ assertTrue(sessionParams.getHardLifetimeSeconds() > sessionParams.getSoftLifetimeSeconds());
+ assertTrue(sessionParams.getSoftLifetimeSeconds() > 0);
+ assertTrue(sessionParams.getDpdDelaySeconds() > 0);
+ assertTrue(sessionParams.getRetransmissionTimeoutsMillis().length > 0);
+ for (int timeout : sessionParams.getRetransmissionTimeoutsMillis()) {
+ assertTrue(timeout > 0);
+ }
+ assertTrue(sessionParams.getConfigurationRequests().isEmpty());
+ assertFalse(sessionParams.hasIkeOption(IKE_OPTION_ACCEPT_ANY_REMOTE_ID));
+ }
+
+ @Test
+ public void testSetLifetimes() throws Exception {
+ IkeSessionParams sessionParams =
+ createIkeParamsBuilderMinimum()
+ .setLifetimeSeconds(HARD_LIFETIME_SECONDS, SOFT_LIFETIME_SECONDS)
+ .build();
+
+ verifyIkeParamsMinimum(sessionParams);
+ assertEquals(HARD_LIFETIME_SECONDS, sessionParams.getHardLifetimeSeconds());
+ assertEquals(SOFT_LIFETIME_SECONDS, sessionParams.getSoftLifetimeSeconds());
+ }
+
+ @Test
+ public void testSetDpdDelay() throws Exception {
+ IkeSessionParams sessionParams =
+ createIkeParamsBuilderMinimum().setDpdDelaySeconds(DPD_DELAY_SECONDS).build();
+
+ verifyIkeParamsMinimum(sessionParams);
+ assertEquals(DPD_DELAY_SECONDS, sessionParams.getDpdDelaySeconds());
+ }
+
+ @Test
+ public void testSetRetransmissionTimeouts() throws Exception {
+ IkeSessionParams sessionParams =
+ createIkeParamsBuilderMinimum()
+ .setRetransmissionTimeoutsMillis(RETRANS_TIMEOUT_MS_LIST)
+ .build();
+
+ verifyIkeParamsMinimum(sessionParams);
+ assertArrayEquals(RETRANS_TIMEOUT_MS_LIST, sessionParams.getRetransmissionTimeoutsMillis());
+ }
+
+ @Test
+ public void testSetPcscfConfigRequests() throws Exception {
+ IkeSessionParams sessionParams =
+ createIkeParamsBuilderMinimum()
+ .setRetransmissionTimeoutsMillis(RETRANS_TIMEOUT_MS_LIST)
+ .addPcscfServerRequest(AF_INET)
+ .addPcscfServerRequest(PCSCF_IPV4_ADDRESS_1)
+ .addPcscfServerRequest(PCSCF_IPV6_ADDRESS_1)
+ .addPcscfServerRequest(AF_INET6)
+ .addPcscfServerRequest(PCSCF_IPV4_ADDRESS_2)
+ .addPcscfServerRequest(PCSCF_IPV6_ADDRESS_2)
+ .build();
+
+ verifyIkeParamsMinimum(sessionParams);
+ verifyConfigRequestTypes(EXPECTED_REQ_COUNT, sessionParams.getConfigurationRequests());
+
+ Set<InetAddress> resultAddresses = new HashSet<>();
+ for (IkeConfigRequest req : sessionParams.getConfigurationRequests()) {
+ if (req instanceof ConfigRequestIpv4PcscfServer
+ && ((ConfigRequestIpv4PcscfServer) req).getAddress() != null) {
+ resultAddresses.add(((ConfigRequestIpv4PcscfServer) req).getAddress());
+ } else if (req instanceof ConfigRequestIpv6PcscfServer
+ && ((ConfigRequestIpv6PcscfServer) req).getAddress() != null) {
+ resultAddresses.add(((ConfigRequestIpv6PcscfServer) req).getAddress());
+ }
+ }
+ assertEquals(EXPECTED_PCSCF_SERVERS, resultAddresses);
+ }
+
+ @Test
+ public void testAddIkeOption() throws Exception {
+ IkeSessionParams sessionParams =
+ createIkeParamsBuilderMinimum()
+ .addIkeOption(IKE_OPTION_ACCEPT_ANY_REMOTE_ID)
+ .build();
+
+ verifyIkeParamsMinimum(sessionParams);
+ assertTrue(sessionParams.hasIkeOption(IKE_OPTION_ACCEPT_ANY_REMOTE_ID));
+ }
+
+ @Test
+ public void testRemoveIkeOption() throws Exception {
+ IkeSessionParams sessionParams =
+ createIkeParamsBuilderMinimum()
+ .addIkeOption(IKE_OPTION_ACCEPT_ANY_REMOTE_ID)
+ .removeIkeOption(IKE_OPTION_ACCEPT_ANY_REMOTE_ID)
+ .build();
+
+ verifyIkeParamsMinimum(sessionParams);
+ assertFalse(sessionParams.hasIkeOption(IKE_OPTION_ACCEPT_ANY_REMOTE_ID));
+ }
+
+ /**
+ * Create a Builder that has minimum configurations to build an IkeSessionParams, except for
+ * authentication method.
+ */
+ private IkeSessionParams.Builder createIkeParamsBuilderMinimumWithoutAuth() {
+ return new IkeSessionParams.Builder(sContext)
+ .setNetwork(mTunNetwork)
+ .setServerHostname(IPV4_ADDRESS_REMOTE.getHostAddress())
+ .addSaProposal(SA_PROPOSAL)
+ .setLocalIdentification(LOCAL_ID)
+ .setRemoteIdentification(REMOTE_ID);
+ }
+
+ /**
+ * Verify the minimum configurations to build an IkeSessionParams, except for authentication
+ * method.
+ *
+ * @see #createIkeParamsBuilderMinimumWithoutAuth
+ */
+ private void verifyIkeParamsMinimumWithoutAuth(IkeSessionParams sessionParams) {
+ assertEquals(mTunNetwork, sessionParams.getNetwork());
+ assertEquals(IPV4_ADDRESS_REMOTE.getHostAddress(), sessionParams.getServerHostname());
+ assertEquals(Arrays.asList(SA_PROPOSAL), sessionParams.getSaProposals());
+ assertEquals(LOCAL_ID, sessionParams.getLocalIdentification());
+ assertEquals(REMOTE_ID, sessionParams.getRemoteIdentification());
+ }
+
+ @Test
+ public void testBuildWithPsk() throws Exception {
+ IkeSessionParams sessionParams =
+ createIkeParamsBuilderMinimumWithoutAuth().setAuthPsk(IKE_PSK).build();
+
+ verifyIkeParamsMinimumWithoutAuth(sessionParams);
+
+ IkeAuthConfig localConfig = sessionParams.getLocalAuthConfig();
+ assertTrue(localConfig instanceof IkeAuthPskConfig);
+ assertArrayEquals(IKE_PSK, ((IkeAuthPskConfig) localConfig).getPsk());
+ IkeAuthConfig remoteConfig = sessionParams.getRemoteAuthConfig();
+ assertTrue(remoteConfig instanceof IkeAuthPskConfig);
+ assertArrayEquals(IKE_PSK, ((IkeAuthPskConfig) remoteConfig).getPsk());
+ }
+
+ @Test
+ public void testBuildWithEap() throws Exception {
+ IkeSessionParams sessionParams =
+ createIkeParamsBuilderMinimumWithoutAuth()
+ .setAuthEap(mServerCaCert, EAP_ALL_METHODS_CONFIG)
+ .build();
+
+ verifyIkeParamsMinimumWithoutAuth(sessionParams);
+
+ IkeAuthConfig localConfig = sessionParams.getLocalAuthConfig();
+ assertTrue(localConfig instanceof IkeAuthEapConfig);
+ assertEquals(EAP_ALL_METHODS_CONFIG, ((IkeAuthEapConfig) localConfig).getEapConfig());
+ IkeAuthConfig remoteConfig = sessionParams.getRemoteAuthConfig();
+ assertTrue(remoteConfig instanceof IkeAuthDigitalSignRemoteConfig);
+ assertEquals(
+ mServerCaCert, ((IkeAuthDigitalSignRemoteConfig) remoteConfig).getRemoteCaCert());
+ }
+
+ @Test
+ public void testBuildWithEapOnlyAuth() throws Exception {
+ IkeSessionParams sessionParams =
+ createIkeParamsBuilderMinimumWithoutAuth()
+ .setAuthEap(mServerCaCert, EAP_ONLY_SAFE_METHODS_CONFIG)
+ .addIkeOption(IKE_OPTION_EAP_ONLY_AUTH)
+ .build();
+
+ assertTrue(sessionParams.hasIkeOption(IKE_OPTION_EAP_ONLY_AUTH));
+ verifyIkeParamsMinimumWithoutAuth(sessionParams);
+
+ IkeAuthConfig localConfig = sessionParams.getLocalAuthConfig();
+ assertTrue(localConfig instanceof IkeAuthEapConfig);
+ assertEquals(EAP_ONLY_SAFE_METHODS_CONFIG, ((IkeAuthEapConfig) localConfig).getEapConfig());
+ IkeAuthConfig remoteConfig = sessionParams.getRemoteAuthConfig();
+ assertTrue(remoteConfig instanceof IkeAuthDigitalSignRemoteConfig);
+ assertEquals(
+ mServerCaCert, ((IkeAuthDigitalSignRemoteConfig) remoteConfig).getRemoteCaCert());
+ }
+
+ @Test
+ public void testThrowBuildEapOnlyAuthWithUnsafeMethod() throws Exception {
+ try {
+ IkeSessionParams sessionParams =
+ createIkeParamsBuilderMinimumWithoutAuth()
+ .setAuthEap(mServerCaCert, EAP_ALL_METHODS_CONFIG)
+ .addIkeOption(IKE_OPTION_EAP_ONLY_AUTH)
+ .build();
+ fail("Expected to fail because EAP only unsafe method is proposed");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildWithDigitalSignature() throws Exception {
+ IkeSessionParams sessionParams =
+ createIkeParamsBuilderMinimumWithoutAuth()
+ .setAuthDigitalSignature(mServerCaCert, mClientEndCert, mClientPrivateKey)
+ .build();
+
+ verifyIkeParamsMinimumWithoutAuth(sessionParams);
+
+ IkeAuthConfig localConfig = sessionParams.getLocalAuthConfig();
+ assertTrue(localConfig instanceof IkeAuthDigitalSignLocalConfig);
+ IkeAuthDigitalSignLocalConfig localSignConfig = (IkeAuthDigitalSignLocalConfig) localConfig;
+ assertEquals(mClientEndCert, localSignConfig.getClientEndCertificate());
+ assertEquals(Collections.EMPTY_LIST, localSignConfig.getIntermediateCertificates());
+ assertEquals(mClientPrivateKey, localSignConfig.getPrivateKey());
+
+ IkeAuthConfig remoteConfig = sessionParams.getRemoteAuthConfig();
+ assertTrue(remoteConfig instanceof IkeAuthDigitalSignRemoteConfig);
+ assertEquals(
+ mServerCaCert, ((IkeAuthDigitalSignRemoteConfig) remoteConfig).getRemoteCaCert());
+ }
+
+ @Test
+ public void testBuildWithDigitalSignatureAndIntermediateCerts() throws Exception {
+ List<X509Certificate> intermediateCerts =
+ Arrays.asList(mClientIntermediateCaCertOne, mClientIntermediateCaCertTwo);
+
+ IkeSessionParams sessionParams =
+ createIkeParamsBuilderMinimumWithoutAuth()
+ .setAuthDigitalSignature(
+ mServerCaCert, mClientEndCert, intermediateCerts, mClientPrivateKey)
+ .build();
+
+ verifyIkeParamsMinimumWithoutAuth(sessionParams);
+
+ IkeAuthConfig localConfig = sessionParams.getLocalAuthConfig();
+ assertTrue(localConfig instanceof IkeAuthDigitalSignLocalConfig);
+ IkeAuthDigitalSignLocalConfig localSignConfig = (IkeAuthDigitalSignLocalConfig) localConfig;
+ assertEquals(mClientEndCert, localSignConfig.getClientEndCertificate());
+ assertEquals(intermediateCerts, localSignConfig.getIntermediateCertificates());
+ assertEquals(mClientPrivateKey, localSignConfig.getPrivateKey());
+
+ IkeAuthConfig remoteConfig = sessionParams.getRemoteAuthConfig();
+ assertTrue(remoteConfig instanceof IkeAuthDigitalSignRemoteConfig);
+ assertEquals(
+ mServerCaCert, ((IkeAuthDigitalSignRemoteConfig) remoteConfig).getRemoteCaCert());
+ }
+}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java
new file mode 100644
index 0000000..ed67dd1
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2020 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.ipsec.ike.cts;
+
+import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_FRAGMENTATION;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static com.android.internal.util.HexDump.hexStringToByteArray;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeSession;
+import android.net.ipsec.ike.IkeSessionConfiguration;
+import android.net.ipsec.ike.IkeSessionConnectionInfo;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
+public class IkeSessionPskTest extends IkeSessionTestBase {
+ // Test vectors for success workflow
+ private static final String SUCCESS_IKE_INIT_RESP =
+ "46B8ECA1E0D72A18B45427679F9245D421202220000000000000015022000030"
+ + "0000002C010100040300000C0100000C800E0080030000080300000203000008"
+ + "0200000200000008040000022800008800020000A7AA3435D088EC1A2B7C2A47"
+ + "1FA1B85F1066C9B2006E7C353FB5B5FDBC2A88347ED2C6F5B7A265D03AE34039"
+ + "6AAC0145CFCC93F8BDB219DDFF22A603B8856A5DC59B6FAB7F17C5660CF38670"
+ + "8794FC72F273ADEB7A4F316519794AED6F8AB61F95DFB360FAF18C6C8CABE471"
+ + "6E18FE215348C2E582171A57FC41146B16C4AFE429000024A634B61C0E5C90C6"
+ + "8D8818B0955B125A9B1DF47BBD18775710792E651083105C2900001C00004004"
+ + "406FA3C5685A16B9B72C7F2EEE9993462C619ABE2900001C00004005AF905A87"
+ + "0A32222AA284A7070585601208A282F0290000080000402E290000100000402F"
+ + "00020003000400050000000800004014";
+ private static final String SUCCESS_IKE_AUTH_RESP =
+ "46B8ECA1E0D72A18B45427679F9245D42E20232000000001000000EC240000D0"
+ + "0D06D37198F3F0962DE8170D66F1A9008267F98CDD956D984BDCED2FC7FAF84A"
+ + "A6664EF25049B46B93C9ED420488E0C172AA6635BF4011C49792EF2B88FE7190"
+ + "E8859FEEF51724FD20C46E7B9A9C3DC4708EF7005707A18AB747C903ABCEAC5C"
+ + "6ECF5A5FC13633DCE3844A920ED10EF202F115DBFBB5D6D2D7AB1F34EB08DE7C"
+ + "A54DCE0A3A582753345CA2D05A0EFDB9DC61E81B2483B7D13EEE0A815D37252C"
+ + "23D2F29E9C30658227D2BB0C9E1A481EAA80BC6BE9006BEDC13E925A755A0290"
+ + "AEC4164D29997F52ED7DCC2E";
+ private static final String SUCCESS_CREATE_CHILD_RESP =
+ "46B8ECA1E0D72A18B45427679F9245D42E20242000000002000000CC210000B0"
+ + "484565D4AF6546274674A8DE339E9C9584EE2326AB9260F41C4D0B6C5B02D1D"
+ + "2E8394E3CDE3094895F2ACCABCDCA8E82960E5196E9622BD13745FC8D6A2BED"
+ + "E561FF5D9975421BC463C959A3CBA3478256B6D278159D99B512DDF56AC1658"
+ + "63C65A986F395FE8B1476124B91F83FD7865304EB95B22CA4DD9601DA7A2533"
+ + "ABF4B36EB1B8CD09522F6A600032316C74E562E6756D9D49D945854E2ABDC4C"
+ + "3AF36305353D60D40B58BE44ABF82";
+ private static final String SUCCESS_DELETE_CHILD_RESP =
+ "46B8ECA1E0D72A18B45427679F9245D42E202520000000030000004C2A000030"
+ + "0C5CEB882DBCA65CE32F4C53909335F1365C91C555316C5E9D9FB553F7AA916"
+ + "EF3A1D93460B7FABAF0B4B854";
+ private static final String SUCCESS_DELETE_IKE_RESP =
+ "46B8ECA1E0D72A18B45427679F9245D42E202520000000040000004C00000030"
+ + "9352D71100777B00ABCC6BD7DBEA697827FFAAA48DF9A54D1D68161939F5DC8"
+ + "6743A7CEB2BE34AC00095A5B8";
+
+ private static final long IKE_INIT_SPI = Long.parseLong("46B8ECA1E0D72A18", 16);
+
+ private static final TunnelModeChildSessionParams CHILD_PARAMS =
+ new TunnelModeChildSessionParams.Builder()
+ .addSaProposal(SaProposalTest.buildChildSaProposalWithNormalModeCipher())
+ .addSaProposal(SaProposalTest.buildChildSaProposalWithCombinedModeCipher())
+ .addInternalAddressRequest(AF_INET)
+ .addInternalAddressRequest(AF_INET6)
+ .build();
+
+ private IkeSessionParams createIkeSessionParams(InetAddress mRemoteAddress) {
+ return new IkeSessionParams.Builder(sContext)
+ .setNetwork(mTunNetwork)
+ .setServerHostname(mRemoteAddress.getHostAddress())
+ .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
+ .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
+ .setLocalIdentification(new IkeFqdnIdentification(LOCAL_HOSTNAME))
+ .setRemoteIdentification(new IkeFqdnIdentification(REMOTE_HOSTNAME))
+ .setAuthPsk(IKE_PSK)
+ .build();
+ }
+
+ private IkeSession openIkeSession(IkeSessionParams ikeParams) {
+ return new IkeSession(
+ sContext,
+ ikeParams,
+ CHILD_PARAMS,
+ mUserCbExecutor,
+ mIkeSessionCallback,
+ mFirstChildSessionCallback);
+ }
+
+ @Test
+ public void testIkeSessionSetupAndManageChildSas() throws Exception {
+ // Open IKE Session
+ IkeSession ikeSession = openIkeSession(createIkeSessionParams(mRemoteAddress));
+ int expectedMsgId = 0;
+ mTunUtils.awaitReqAndInjectResp(
+ IKE_INIT_SPI,
+ expectedMsgId++,
+ false /* expectedUseEncap */,
+ hexStringToByteArray(SUCCESS_IKE_INIT_RESP));
+
+ mTunUtils.awaitReqAndInjectResp(
+ IKE_INIT_SPI,
+ expectedMsgId++,
+ true /* expectedUseEncap */,
+ hexStringToByteArray(SUCCESS_IKE_AUTH_RESP));
+
+ // Verify opening IKE Session
+ IkeSessionConfiguration ikeConfig = mIkeSessionCallback.awaitIkeConfig();
+ assertNotNull(ikeConfig);
+ assertEquals(EXPECTED_REMOTE_APP_VERSION_EMPTY, ikeConfig.getRemoteApplicationVersion());
+ assertTrue(ikeConfig.getRemoteVendorIds().isEmpty());
+ assertTrue(ikeConfig.getPcscfServers().isEmpty());
+ assertTrue(ikeConfig.isIkeExtensionEnabled(EXTENSION_TYPE_FRAGMENTATION));
+
+ IkeSessionConnectionInfo ikeConnectInfo = ikeConfig.getIkeSessionConnectionInfo();
+ assertNotNull(ikeConnectInfo);
+ assertEquals(mLocalAddress, ikeConnectInfo.getLocalAddress());
+ assertEquals(mRemoteAddress, ikeConnectInfo.getRemoteAddress());
+ assertEquals(mTunNetwork, ikeConnectInfo.getNetwork());
+
+ // Verify opening first Child Session
+ ChildSessionConfiguration firstChildConfig = mFirstChildSessionCallback.awaitChildConfig();
+ assertNotNull(firstChildConfig);
+ assertEquals(
+ Arrays.asList(EXPECTED_INBOUND_TS), firstChildConfig.getInboundTrafficSelectors());
+ assertEquals(Arrays.asList(DEFAULT_V4_TS), firstChildConfig.getOutboundTrafficSelectors());
+ assertEquals(
+ Arrays.asList(EXPECTED_INTERNAL_LINK_ADDR),
+ firstChildConfig.getInternalAddresses());
+ assertTrue(firstChildConfig.getInternalSubnets().isEmpty());
+ assertTrue(firstChildConfig.getInternalDnsServers().isEmpty());
+ assertTrue(firstChildConfig.getInternalDhcpServers().isEmpty());
+
+ // Open additional Child Session
+ TestChildSessionCallback additionalChildCb = new TestChildSessionCallback();
+ ikeSession.openChildSession(CHILD_PARAMS, additionalChildCb);
+ mTunUtils.awaitReqAndInjectResp(
+ IKE_INIT_SPI,
+ expectedMsgId++,
+ true /* expectedUseEncap */,
+ hexStringToByteArray(SUCCESS_CREATE_CHILD_RESP));
+
+ // Verify opening additional Child Session
+ ChildSessionConfiguration additionalChildConfig = additionalChildCb.awaitChildConfig();
+ assertNotNull(additionalChildConfig);
+ assertEquals(
+ Arrays.asList(EXPECTED_INBOUND_TS), firstChildConfig.getInboundTrafficSelectors());
+ assertEquals(Arrays.asList(DEFAULT_V4_TS), firstChildConfig.getOutboundTrafficSelectors());
+ assertTrue(additionalChildConfig.getInternalAddresses().isEmpty());
+ assertTrue(firstChildConfig.getInternalSubnets().isEmpty());
+ assertTrue(firstChildConfig.getInternalDnsServers().isEmpty());
+ assertTrue(firstChildConfig.getInternalDhcpServers().isEmpty());
+
+ // Close additional Child Session
+ ikeSession.closeChildSession(additionalChildCb);
+ mTunUtils.awaitReqAndInjectResp(
+ IKE_INIT_SPI,
+ expectedMsgId++,
+ true /* expectedUseEncap */,
+ hexStringToByteArray(SUCCESS_DELETE_CHILD_RESP));
+
+ additionalChildCb.awaitOnClosed();
+
+ // Close IKE Session
+ ikeSession.close();
+ mTunUtils.awaitReqAndInjectResp(
+ IKE_INIT_SPI,
+ expectedMsgId++,
+ true /* expectedUseEncap */,
+ hexStringToByteArray(SUCCESS_DELETE_IKE_RESP));
+
+ mFirstChildSessionCallback.awaitOnClosed();
+ mIkeSessionCallback.awaitOnClosed();
+
+ // TODO: verify IpSecTransform pair is created and deleted
+ }
+
+ @Test
+ public void testIkeSessionKill() throws Exception {
+ // Open IKE Session
+ IkeSession ikeSession = openIkeSession(createIkeSessionParams(mRemoteAddress));
+ int expectedMsgId = 0;
+ mTunUtils.awaitReqAndInjectResp(
+ IKE_INIT_SPI,
+ expectedMsgId++,
+ false /* expectedUseEncap */,
+ hexStringToByteArray(SUCCESS_IKE_INIT_RESP));
+
+ mTunUtils.awaitReqAndInjectResp(
+ IKE_INIT_SPI,
+ expectedMsgId++,
+ true /* expectedUseEncap */,
+ hexStringToByteArray(SUCCESS_IKE_AUTH_RESP));
+
+ ikeSession.kill();
+
+ mFirstChildSessionCallback.awaitOnClosed();
+ mIkeSessionCallback.awaitOnClosed();
+ }
+
+ @Test
+ public void testIkeInitFail() throws Exception {
+ String ikeInitFailRespHex =
+ "46B8ECA1E0D72A180000000000000000292022200000000000000024000000080000000E";
+
+ // Open IKE Session
+ IkeSession ikeSession = openIkeSession(createIkeSessionParams(mRemoteAddress));
+ int expectedMsgId = 0;
+ mTunUtils.awaitReqAndInjectResp(
+ IKE_INIT_SPI,
+ expectedMsgId++,
+ false /* expectedUseEncap */,
+ hexStringToByteArray(ikeInitFailRespHex));
+
+ IkeException exception = mIkeSessionCallback.awaitOnClosedException();
+ assertNotNull(exception);
+ assertTrue(exception instanceof IkeProtocolException);
+ IkeProtocolException protocolException = (IkeProtocolException) exception;
+ assertEquals(ERROR_TYPE_NO_PROPOSAL_CHOSEN, protocolException.getErrorType());
+ assertArrayEquals(EXPECTED_PROTOCOL_ERROR_DATA_NONE, protocolException.getErrorData());
+ }
+
+ // TODO(b/148689509): Verify rekey process and handling IKE_AUTH failure
+}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java
new file mode 100644
index 0000000..deba8fd
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2020 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
+ *
+ * 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.ipsec.ike.cts;
+
+import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.InetAddresses;
+import android.net.IpSecTransform;
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.TestNetworkInterface;
+import android.net.TestNetworkManager;
+import android.net.annotations.PolicyDirection;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionConfiguration;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.cts.TestNetworkUtils.TestNetworkCallback;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.testutils.ArrayTrackRecord;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Package private base class for testing IkeSessionParams and IKE exchanges.
+ *
+ * <p>Subclasses MUST explicitly call #setUpTestNetwork and #tearDownTestNetwork to be able to use
+ * the test network
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
+abstract class IkeSessionTestBase extends IkeTestBase {
+ // Package-wide common expected results that will be shared by all IKE/Child SA creation tests
+ static final String EXPECTED_REMOTE_APP_VERSION_EMPTY = "";
+ static final byte[] EXPECTED_PROTOCOL_ERROR_DATA_NONE = new byte[0];
+ static final InetAddress EXPECTED_INTERNAL_ADDR =
+ InetAddresses.parseNumericAddress("198.51.100.10");
+ static final LinkAddress EXPECTED_INTERNAL_LINK_ADDR =
+ new LinkAddress(EXPECTED_INTERNAL_ADDR, IP4_PREFIX_LEN);
+ static final IkeTrafficSelector EXPECTED_INBOUND_TS =
+ new IkeTrafficSelector(
+ MIN_PORT, MAX_PORT, EXPECTED_INTERNAL_ADDR, EXPECTED_INTERNAL_ADDR);
+
+ // Static state to reduce setup/teardown
+ static Context sContext = InstrumentationRegistry.getContext();
+ static ConnectivityManager sCM =
+ (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ static TestNetworkManager sTNM;
+
+ private static final int TIMEOUT_MS = 500;
+
+ // Constants to be used for providing different IP addresses for each tests
+ private static final byte IP_ADDR_LAST_BYTE_MAX = (byte) 100;
+ private static final byte[] INITIAL_AVAILABLE_IP4_ADDR_LOCAL =
+ InetAddresses.parseNumericAddress("192.0.2.1").getAddress();
+ private static final byte[] INITIAL_AVAILABLE_IP4_ADDR_REMOTE =
+ InetAddresses.parseNumericAddress("198.51.100.1").getAddress();
+ private static final byte[] NEXT_AVAILABLE_IP4_ADDR_LOCAL = INITIAL_AVAILABLE_IP4_ADDR_LOCAL;
+ private static final byte[] NEXT_AVAILABLE_IP4_ADDR_REMOTE = INITIAL_AVAILABLE_IP4_ADDR_REMOTE;
+
+ ParcelFileDescriptor mTunFd;
+ TestNetworkCallback mTunNetworkCallback;
+ Network mTunNetwork;
+ IkeTunUtils mTunUtils;
+
+ InetAddress mLocalAddress;
+ InetAddress mRemoteAddress;
+
+ Executor mUserCbExecutor;
+ TestIkeSessionCallback mIkeSessionCallback;
+ TestChildSessionCallback mFirstChildSessionCallback;
+
+ // This method is guaranteed to run in subclasses and will run before subclasses' @BeforeClass
+ // methods.
+ @BeforeClass
+ public static void setUpPermissionBeforeClass() throws Exception {
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity();
+ sTNM = (TestNetworkManager) sContext.getSystemService(Context.TEST_NETWORK_SERVICE);
+
+ // Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted, and
+ // a standard permission is insufficient. So we shell out the appop, to give us the
+ // right appop permissions.
+ setAppOp(OP_MANAGE_IPSEC_TUNNELS, true);
+ }
+
+ // This method is guaranteed to run in subclasses and will run after subclasses' @AfterClass
+ // methods.
+ @AfterClass
+ public static void tearDownPermissionAfterClass() throws Exception {
+ setAppOp(OP_MANAGE_IPSEC_TUNNELS, false);
+
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mLocalAddress = getNextAvailableIpv4AddressLocal();
+ mRemoteAddress = getNextAvailableIpv4AddressRemote();
+ setUpTestNetwork(mLocalAddress);
+
+ mUserCbExecutor = Executors.newSingleThreadExecutor();
+ mIkeSessionCallback = new TestIkeSessionCallback();
+ mFirstChildSessionCallback = new TestChildSessionCallback();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ tearDownTestNetwork();
+
+ resetNextAvailableAddress(NEXT_AVAILABLE_IP4_ADDR_LOCAL, INITIAL_AVAILABLE_IP4_ADDR_LOCAL);
+ resetNextAvailableAddress(
+ NEXT_AVAILABLE_IP4_ADDR_REMOTE, INITIAL_AVAILABLE_IP4_ADDR_REMOTE);
+ }
+
+ void setUpTestNetwork(InetAddress localAddr) throws Exception {
+ int prefixLen = localAddr instanceof Inet4Address ? IP4_PREFIX_LEN : IP4_PREFIX_LEN;
+
+ TestNetworkInterface testIface =
+ sTNM.createTunInterface(new LinkAddress[] {new LinkAddress(localAddr, prefixLen)});
+
+ mTunFd = testIface.getFileDescriptor();
+ mTunNetworkCallback =
+ TestNetworkUtils.setupAndGetTestNetwork(
+ sCM, sTNM, testIface.getInterfaceName(), new Binder());
+ mTunNetwork = mTunNetworkCallback.getNetworkBlocking();
+ mTunUtils = new IkeTunUtils(mTunFd);
+ }
+
+ void tearDownTestNetwork() throws Exception {
+ sCM.unregisterNetworkCallback(mTunNetworkCallback);
+
+ sTNM.teardownTestNetwork(mTunNetwork);
+ mTunFd.close();
+ }
+
+ private static void setAppOp(int appop, boolean allow) {
+ String opName = AppOpsManager.opToName(appop);
+ for (String pkg : new String[] {"com.android.shell", sContext.getPackageName()}) {
+ String cmd =
+ String.format(
+ "appops set %s %s %s",
+ pkg, // Package name
+ opName, // Appop
+ (allow ? "allow" : "deny")); // Action
+ Log.d("IKE", "CTS setAppOp cmd " + cmd);
+
+ String result = SystemUtil.runShellCommand(cmd);
+ }
+ }
+
+ Inet4Address getNextAvailableIpv4AddressLocal() throws Exception {
+ return (Inet4Address)
+ getNextAvailableAddress(
+ NEXT_AVAILABLE_IP4_ADDR_LOCAL,
+ INITIAL_AVAILABLE_IP4_ADDR_LOCAL,
+ false /* isIp6 */);
+ }
+
+ Inet4Address getNextAvailableIpv4AddressRemote() throws Exception {
+ return (Inet4Address)
+ getNextAvailableAddress(
+ NEXT_AVAILABLE_IP4_ADDR_REMOTE,
+ INITIAL_AVAILABLE_IP4_ADDR_REMOTE,
+ false /* isIp6 */);
+ }
+
+ InetAddress getNextAvailableAddress(
+ byte[] nextAddressBytes, byte[] initialAddressBytes, boolean isIp6) throws Exception {
+ int addressLen = isIp6 ? IP6_ADDRESS_LEN : IP4_ADDRESS_LEN;
+
+ synchronized (nextAddressBytes) {
+ if (nextAddressBytes[addressLen - 1] == IP_ADDR_LAST_BYTE_MAX) {
+ resetNextAvailableAddress(nextAddressBytes, initialAddressBytes);
+ }
+
+ InetAddress address = InetAddress.getByAddress(nextAddressBytes);
+ nextAddressBytes[addressLen - 1]++;
+ return address;
+ }
+ }
+
+ private void resetNextAvailableAddress(byte[] nextAddressBytes, byte[] initialAddressBytes) {
+ synchronized (nextAddressBytes) {
+ System.arraycopy(
+ nextAddressBytes, 0, initialAddressBytes, 0, initialAddressBytes.length);
+ }
+ }
+
+ static class TestIkeSessionCallback implements IkeSessionCallback {
+ private CompletableFuture<IkeSessionConfiguration> mFutureIkeConfig =
+ new CompletableFuture<>();
+ private CompletableFuture<Boolean> mFutureOnClosedCall = new CompletableFuture<>();
+ private CompletableFuture<IkeException> mFutureOnClosedException =
+ new CompletableFuture<>();
+
+ private int mOnErrorExceptionsCount = 0;
+ private ArrayTrackRecord<IkeProtocolException> mOnErrorExceptionsTrackRecord =
+ new ArrayTrackRecord<>();
+
+ @Override
+ public void onOpened(@NonNull IkeSessionConfiguration sessionConfiguration) {
+ mFutureIkeConfig.complete(sessionConfiguration);
+ }
+
+ @Override
+ public void onClosed() {
+ mFutureOnClosedCall.complete(true /* unused */);
+ }
+
+ @Override
+ public void onClosedExceptionally(@NonNull IkeException exception) {
+ mFutureOnClosedException.complete(exception);
+ }
+
+ @Override
+ public void onError(@NonNull IkeProtocolException exception) {
+ mOnErrorExceptionsTrackRecord.add(exception);
+ }
+
+ public IkeSessionConfiguration awaitIkeConfig() throws Exception {
+ return mFutureIkeConfig.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+
+ public IkeException awaitOnClosedException() throws Exception {
+ return mFutureOnClosedException.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+
+ public IkeProtocolException awaitNextOnErrorException() {
+ return mOnErrorExceptionsTrackRecord.poll(
+ (long) TIMEOUT_MS,
+ mOnErrorExceptionsCount++,
+ (transform) -> {
+ return true;
+ });
+ }
+
+ public void awaitOnClosed() throws Exception {
+ mFutureOnClosedCall.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ static class TestChildSessionCallback implements ChildSessionCallback {
+ private CompletableFuture<ChildSessionConfiguration> mFutureChildConfig =
+ new CompletableFuture<>();
+ private CompletableFuture<Boolean> mFutureOnClosedCall = new CompletableFuture<>();
+ private CompletableFuture<IkeException> mFutureOnClosedException =
+ new CompletableFuture<>();
+
+ private int mCreatedIpSecTransformCount = 0;
+ private int mDeletedIpSecTransformCount = 0;
+ private ArrayTrackRecord<IpSecTransformCallRecord> mCreatedIpSecTransformsTrackRecord =
+ new ArrayTrackRecord<>();
+ private ArrayTrackRecord<IpSecTransformCallRecord> mDeletedIpSecTransformsTrackRecord =
+ new ArrayTrackRecord<>();
+
+ @Override
+ public void onOpened(@NonNull ChildSessionConfiguration sessionConfiguration) {
+ mFutureChildConfig.complete(sessionConfiguration);
+ }
+
+ @Override
+ public void onClosed() {
+ mFutureOnClosedCall.complete(true /* unused */);
+ }
+
+ @Override
+ public void onClosedExceptionally(@NonNull IkeException exception) {
+ mFutureOnClosedException.complete(exception);
+ }
+
+ @Override
+ public void onIpSecTransformCreated(@NonNull IpSecTransform ipSecTransform, int direction) {
+ mCreatedIpSecTransformsTrackRecord.add(
+ new IpSecTransformCallRecord(ipSecTransform, direction));
+ }
+
+ @Override
+ public void onIpSecTransformDeleted(@NonNull IpSecTransform ipSecTransform, int direction) {
+ mDeletedIpSecTransformsTrackRecord.add(
+ new IpSecTransformCallRecord(ipSecTransform, direction));
+ }
+
+ public ChildSessionConfiguration awaitChildConfig() throws Exception {
+ return mFutureChildConfig.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+
+ public IkeException awaitOnClosedException() throws Exception {
+ return mFutureOnClosedException.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+
+ public IpSecTransformCallRecord awaitNextCreatedIpSecTransform() {
+ return mCreatedIpSecTransformsTrackRecord.poll(
+ (long) TIMEOUT_MS,
+ mCreatedIpSecTransformCount++,
+ (transform) -> {
+ return true;
+ });
+ }
+
+ public IpSecTransformCallRecord awaitNextDeletedIpSecTransform() {
+ return mDeletedIpSecTransformsTrackRecord.poll(
+ (long) TIMEOUT_MS,
+ mDeletedIpSecTransformCount++,
+ (transform) -> {
+ return true;
+ });
+ }
+
+ public void awaitOnClosed() throws Exception {
+ mFutureOnClosedCall.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ /**
+ * This class represents a created or deleted IpSecTransfrom that is provided by
+ * ChildSessionCallback
+ */
+ static class IpSecTransformCallRecord {
+ public final IpSecTransform ipSecTransform;
+ public final int direction;
+
+ IpSecTransformCallRecord(IpSecTransform ipSecTransform, @PolicyDirection int direction) {
+ this.ipSecTransform = ipSecTransform;
+ this.direction = direction;
+ }
+ }
+
+ // TODO(b/148689509): Verify IKE Session setup using EAP and digital-signature-based auth
+
+ // TODO(b/148689509): Verify hostname based creation
+}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java
new file mode 100644
index 0000000..f07c710
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2020 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.ipsec.ike.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.net.InetAddresses;
+import android.net.ipsec.ike.IkeTrafficSelector;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Shared parameters and util methods for testing different components of IKE */
+abstract class IkeTestBase {
+ static final int MIN_PORT = 0;
+ static final int MAX_PORT = 65535;
+ private static final int INBOUND_TS_START_PORT = MIN_PORT;
+ private static final int INBOUND_TS_END_PORT = 65520;
+ private static final int OUTBOUND_TS_START_PORT = 16;
+ private static final int OUTBOUND_TS_END_PORT = MAX_PORT;
+
+ static final int IP4_ADDRESS_LEN = 4;
+ static final int IP6_ADDRESS_LEN = 16;
+ static final int IP4_PREFIX_LEN = 32;
+ static final int IP6_PREFIX_LEN = 64;
+
+ static final byte[] IKE_PSK = "ikeAndroidPsk".getBytes();
+
+ static final String LOCAL_HOSTNAME = "client.test.ike.android.net";
+ static final String REMOTE_HOSTNAME = "server.test.ike.android.net";
+ static final String LOCAL_ASN1_DN_STRING = "CN=client.test.ike.android.net, O=Android, C=US";
+ static final String LOCAL_RFC822_NAME = "client.test.ike@example.com";
+ static final byte[] LOCAL_KEY_ID = "Local Key ID".getBytes();
+
+ static final int SUB_ID = 1;
+ static final byte[] EAP_IDENTITY = "test@android.net".getBytes();
+ static final String NETWORK_NAME = "android.net";
+ static final String EAP_MSCHAPV2_USERNAME = "username";
+ static final String EAP_MSCHAPV2_PASSWORD = "password";
+
+ static final Inet4Address IPV4_ADDRESS_LOCAL =
+ (Inet4Address) (InetAddresses.parseNumericAddress("192.0.2.100"));
+ static final Inet4Address IPV4_ADDRESS_REMOTE =
+ (Inet4Address) (InetAddresses.parseNumericAddress("198.51.100.100"));
+ static final Inet6Address IPV6_ADDRESS_LOCAL =
+ (Inet6Address) (InetAddresses.parseNumericAddress("2001:db8::100"));
+ static final Inet6Address IPV6_ADDRESS_REMOTE =
+ (Inet6Address) (InetAddresses.parseNumericAddress("2001:db8:255::100"));
+
+ static final InetAddress PCSCF_IPV4_ADDRESS_1 = InetAddresses.parseNumericAddress("192.0.2.1");
+ static final InetAddress PCSCF_IPV4_ADDRESS_2 = InetAddresses.parseNumericAddress("192.0.2.2");
+ static final InetAddress PCSCF_IPV6_ADDRESS_1 =
+ InetAddresses.parseNumericAddress("2001:DB8::1");
+ static final InetAddress PCSCF_IPV6_ADDRESS_2 =
+ InetAddresses.parseNumericAddress("2001:DB8::2");
+
+ static final IkeTrafficSelector DEFAULT_V4_TS =
+ new IkeTrafficSelector(
+ MIN_PORT,
+ MAX_PORT,
+ InetAddresses.parseNumericAddress("0.0.0.0"),
+ InetAddresses.parseNumericAddress("255.255.255.255"));
+ static final IkeTrafficSelector DEFAULT_V6_TS =
+ new IkeTrafficSelector(
+ MIN_PORT,
+ MAX_PORT,
+ InetAddresses.parseNumericAddress("::"),
+ InetAddresses.parseNumericAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
+ static final IkeTrafficSelector INBOUND_V4_TS =
+ new IkeTrafficSelector(
+ INBOUND_TS_START_PORT,
+ INBOUND_TS_END_PORT,
+ InetAddresses.parseNumericAddress("192.0.2.10"),
+ InetAddresses.parseNumericAddress("192.0.2.20"));
+ static final IkeTrafficSelector OUTBOUND_V4_TS =
+ new IkeTrafficSelector(
+ OUTBOUND_TS_START_PORT,
+ OUTBOUND_TS_END_PORT,
+ InetAddresses.parseNumericAddress("198.51.100.0"),
+ InetAddresses.parseNumericAddress("198.51.100.255"));
+
+ static final IkeTrafficSelector INBOUND_V6_TS =
+ new IkeTrafficSelector(
+ INBOUND_TS_START_PORT,
+ INBOUND_TS_END_PORT,
+ InetAddresses.parseNumericAddress("2001:db8::10"),
+ InetAddresses.parseNumericAddress("2001:db8::128"));
+ static final IkeTrafficSelector OUTBOUND_V6_TS =
+ new IkeTrafficSelector(
+ OUTBOUND_TS_START_PORT,
+ OUTBOUND_TS_END_PORT,
+ InetAddresses.parseNumericAddress("2001:db8:255::64"),
+ InetAddresses.parseNumericAddress("2001:db8:255::255"));
+
+ // Verify Config requests in TunnelModeChildSessionParams and IkeSessionParams
+ <T> void verifyConfigRequestTypes(
+ Map<Class<? extends T>, Integer> expectedReqCntMap, List<? extends T> resultReqList) {
+ Map<Class<? extends T>, Integer> resultReqCntMap = new HashMap<>();
+
+ // Verify that every config request type in resultReqList is expected, and build
+ // resultReqCntMap at the same time
+ for (T resultReq : resultReqList) {
+ boolean isResultReqExpected = false;
+
+ for (Class<? extends T> expectedReqInterface : expectedReqCntMap.keySet()) {
+ if (expectedReqInterface.isInstance(resultReq)) {
+ isResultReqExpected = true;
+
+ resultReqCntMap.put(
+ expectedReqInterface,
+ resultReqCntMap.getOrDefault(expectedReqInterface, 0) + 1);
+ }
+ }
+
+ if (!isResultReqExpected) {
+ fail("Failed due to unexpected config request " + resultReq);
+ }
+ }
+
+ assertEquals(expectedReqCntMap, resultReqCntMap);
+
+ // TODO: Think of a neat way to validate both counts and values in this method. Probably can
+ // build Runnables as validators for count and values.
+ }
+}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java
new file mode 100644
index 0000000..5a8258d
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2020 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.ipsec.ike.cts;
+
+import static android.net.ipsec.ike.cts.PacketUtils.BytePayload;
+import static android.net.ipsec.ike.cts.PacketUtils.IP4_HDRLEN;
+import static android.net.ipsec.ike.cts.PacketUtils.IP6_HDRLEN;
+import static android.net.ipsec.ike.cts.PacketUtils.Ip4Header;
+import static android.net.ipsec.ike.cts.PacketUtils.Ip6Header;
+import static android.net.ipsec.ike.cts.PacketUtils.IpHeader;
+import static android.net.ipsec.ike.cts.PacketUtils.Payload;
+import static android.net.ipsec.ike.cts.PacketUtils.UDP_HDRLEN;
+import static android.net.ipsec.ike.cts.PacketUtils.UdpHeader;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static org.junit.Assert.fail;
+
+import android.os.ParcelFileDescriptor;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+public class IkeTunUtils extends TunUtils {
+ private static final int PORT_LEN = 2;
+
+ private static final int NON_ESP_MARKER_LEN = 4;
+ private static final byte[] NON_ESP_MARKER = new byte[NON_ESP_MARKER_LEN];
+
+ private static final int IKE_HEADER_LEN = 28;
+ private static final int IKE_INIT_SPI_OFFSET = 0;
+ private static final int IKE_IS_RESP_BYTE_OFFSET = 19;
+ private static final int IKE_MSG_ID_OFFSET = 20;
+
+ public IkeTunUtils(ParcelFileDescriptor tunFd) {
+ super(tunFd);
+ }
+
+ /**
+ * Await the expected IKE request and inject an IKE response.
+ *
+ * @param respIkePkt IKE response packet without IP/UDP headers or NON ESP MARKER.
+ */
+ public byte[] awaitReqAndInjectResp(
+ long expectedInitIkeSpi, int expectedMsgId, boolean expectedUseEncap, byte[] respIkePkt)
+ throws Exception {
+ byte[] request =
+ awaitIkePacket(
+ expectedInitIkeSpi,
+ expectedMsgId,
+ false /* expectedResp */,
+ expectedUseEncap);
+
+ // Build response header by flipping address and port
+ InetAddress srcAddr = getAddress(request, false /* shouldGetSource */);
+ InetAddress dstAddr = getAddress(request, true /* shouldGetSource */);
+ int srcPort = getPort(request, false /* shouldGetSource */);
+ int dstPort = getPort(request, true /* shouldGetSource */);
+
+ byte[] response =
+ buildIkePacket(srcAddr, dstAddr, srcPort, dstPort, expectedUseEncap, respIkePkt);
+ injectPacket(response);
+ return request;
+ }
+
+ private byte[] awaitIkePacket(
+ long expectedInitIkeSpi,
+ int expectedMsgId,
+ boolean expectedResp,
+ boolean expectedUseEncap)
+ throws Exception {
+ long endTime = System.currentTimeMillis() + TIMEOUT;
+ int startIndex = 0;
+ synchronized (mPackets) {
+ while (System.currentTimeMillis() < endTime) {
+ byte[] ikePkt =
+ getFirstMatchingPacket(
+ (pkt) -> {
+ return isIke(
+ pkt,
+ expectedInitIkeSpi,
+ expectedMsgId,
+ expectedResp,
+ expectedUseEncap);
+ },
+ startIndex);
+ if (ikePkt != null) {
+ return ikePkt; // We've found the packet we're looking for.
+ }
+
+ startIndex = mPackets.size();
+
+ // Try to prevent waiting too long. If waitTimeout <= 0, we've already hit timeout
+ long waitTimeout = endTime - System.currentTimeMillis();
+ if (waitTimeout > 0) {
+ mPackets.wait(waitTimeout);
+ }
+ }
+
+ String direction = expectedResp ? "response" : "request";
+ fail(
+ "No such IKE "
+ + direction
+ + " found with Initiator SPI "
+ + expectedInitIkeSpi
+ + " and message ID "
+ + expectedMsgId);
+ }
+ return null;
+ }
+
+ private static boolean isIke(
+ byte[] pkt,
+ long expectedInitIkeSpi,
+ int expectedMsgId,
+ boolean expectedResp,
+ boolean expectedUseEncap) {
+ int ipProtocolOffset = 0;
+ int ikeOffset = 0;
+ if (isIpv6(pkt)) {
+ // IPv6 UDP expectedUseEncap not supported by kernels; assume non-expectedUseEncap.
+ ipProtocolOffset = IP6_PROTO_OFFSET;
+ ikeOffset = IP6_HDRLEN + UDP_HDRLEN;
+ } else {
+ // Use default IPv4 header length (assuming no options)
+ ipProtocolOffset = IP4_PROTO_OFFSET;
+ ikeOffset = IP4_HDRLEN + UDP_HDRLEN;
+
+ if (expectedUseEncap) {
+ if (hasNonEspMarker(pkt)) {
+ ikeOffset += NON_ESP_MARKER_LEN;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ return pkt[ipProtocolOffset] == IPPROTO_UDP
+ && areSpiAndMsgIdEqual(
+ pkt, ikeOffset, expectedInitIkeSpi, expectedMsgId, expectedResp);
+ }
+
+ private static boolean hasNonEspMarker(byte[] pkt) {
+ ByteBuffer buffer = ByteBuffer.wrap(pkt);
+ int ikeOffset = IP4_HDRLEN + UDP_HDRLEN;
+ if (buffer.remaining() < ikeOffset) return false;
+
+ buffer.get(new byte[ikeOffset]); // Skip IP and UDP header
+ byte[] nonEspMarker = new byte[NON_ESP_MARKER_LEN];
+ if (buffer.remaining() < NON_ESP_MARKER_LEN) return false;
+
+ buffer.get(nonEspMarker);
+ return Arrays.equals(NON_ESP_MARKER, nonEspMarker);
+ }
+
+ private static boolean areSpiAndMsgIdEqual(
+ byte[] pkt,
+ int ikeOffset,
+ long expectedIkeInitSpi,
+ int expectedMsgId,
+ boolean expectedResp) {
+ if (pkt.length <= ikeOffset + IKE_HEADER_LEN) return false;
+
+ ByteBuffer buffer = ByteBuffer.wrap(pkt);
+ buffer.get(new byte[ikeOffset]); // Skip IP, UDP header (and NON_ESP_MARKER)
+
+ // Check message ID.
+ buffer.get(new byte[IKE_MSG_ID_OFFSET]);
+ int msgId = buffer.getInt();
+ return expectedMsgId == msgId;
+
+ // TODO: Check SPI and packet direction
+ }
+
+ private static InetAddress getAddress(byte[] pkt, boolean shouldGetSource) throws Exception {
+ int ipLen = isIpv6(pkt) ? IP6_ADDR_LEN : IP4_ADDR_LEN;
+ int srcIpOffset = isIpv6(pkt) ? IP6_ADDR_OFFSET : IP4_ADDR_OFFSET;
+ int ipOffset = shouldGetSource ? srcIpOffset : srcIpOffset + ipLen;
+
+ ByteBuffer buffer = ByteBuffer.wrap(pkt);
+ buffer.get(new byte[ipOffset]);
+ byte[] ipAddrBytes = new byte[ipLen];
+ buffer.get(ipAddrBytes);
+ return InetAddress.getByAddress(ipAddrBytes);
+ }
+
+ private static int getPort(byte[] pkt, boolean shouldGetSource) {
+ ByteBuffer buffer = ByteBuffer.wrap(pkt);
+ int srcPortOffset = isIpv6(pkt) ? IP6_HDRLEN : IP4_HDRLEN;
+ int portOffset = shouldGetSource ? srcPortOffset : srcPortOffset + PORT_LEN;
+
+ buffer.get(new byte[portOffset]);
+ return Short.toUnsignedInt(buffer.getShort());
+ }
+
+ private static byte[] buildIkePacket(
+ InetAddress srcAddr,
+ InetAddress dstAddr,
+ int srcPort,
+ int dstPort,
+ boolean useEncap,
+ byte[] ikePacket)
+ throws Exception {
+ if (useEncap) {
+ ByteBuffer buffer = ByteBuffer.allocate(NON_ESP_MARKER_LEN + ikePacket.length);
+ buffer.put(NON_ESP_MARKER);
+ buffer.put(ikePacket);
+ ikePacket = buffer.array();
+ }
+
+ UdpHeader udpPkt = new UdpHeader(srcPort, dstPort, new BytePayload(ikePacket));
+ IpHeader ipPkt = getIpHeader(udpPkt.getProtocolId(), srcAddr, dstAddr, udpPkt);
+ return ipPkt.getPacketBytes();
+ }
+
+ private static IpHeader getIpHeader(
+ int protocol, InetAddress src, InetAddress dst, Payload payload) {
+ if ((src instanceof Inet6Address) != (dst instanceof Inet6Address)) {
+ throw new IllegalArgumentException("Invalid src/dst address combination");
+ }
+
+ if (src instanceof Inet6Address) {
+ return new Ip6Header(protocol, (Inet6Address) src, (Inet6Address) dst, payload);
+ } else {
+ return new Ip4Header(protocol, (Inet4Address) src, (Inet4Address) dst, payload);
+ }
+ }
+}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/PacketUtils.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/PacketUtils.java
new file mode 100644
index 0000000..35e6719
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/PacketUtils.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2020 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.ipsec.ike.cts;
+
+import static android.system.OsConstants.IPPROTO_IPV6;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * This code is a exact copy of {@link PacketUtils} in
+ * cts/tests/tests/net/src/android/net/cts/PacketUtils.java.
+ *
+ * <p>TODO(b/148689509): Statically include the PacketUtils source file instead of copying it.
+ */
+public class PacketUtils {
+ private static final String TAG = PacketUtils.class.getSimpleName();
+
+ private static final int DATA_BUFFER_LEN = 4096;
+
+ static final int IP4_HDRLEN = 20;
+ static final int IP6_HDRLEN = 40;
+ static final int UDP_HDRLEN = 8;
+ static final int TCP_HDRLEN = 20;
+ static final int TCP_HDRLEN_WITH_TIMESTAMP_OPT = TCP_HDRLEN + 12;
+
+ // Not defined in OsConstants
+ static final int IPPROTO_IPV4 = 4;
+ static final int IPPROTO_ESP = 50;
+
+ // Encryption parameters
+ static final int AES_GCM_IV_LEN = 8;
+ static final int AES_CBC_IV_LEN = 16;
+ static final int AES_GCM_BLK_SIZE = 4;
+ static final int AES_CBC_BLK_SIZE = 16;
+
+ // Encryption algorithms
+ static final String AES = "AES";
+ static final String AES_CBC = "AES/CBC/NoPadding";
+ static final String HMAC_SHA_256 = "HmacSHA256";
+
+ public interface Payload {
+ byte[] getPacketBytes(IpHeader header) throws Exception;
+
+ void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) throws Exception;
+
+ short length();
+
+ int getProtocolId();
+ }
+
+ public abstract static class IpHeader {
+
+ public final byte proto;
+ public final InetAddress srcAddr;
+ public final InetAddress dstAddr;
+ public final Payload payload;
+
+ public IpHeader(int proto, InetAddress src, InetAddress dst, Payload payload) {
+ this.proto = (byte) proto;
+ this.srcAddr = src;
+ this.dstAddr = dst;
+ this.payload = payload;
+ }
+
+ public abstract byte[] getPacketBytes() throws Exception;
+
+ public abstract int getProtocolId();
+ }
+
+ public static class Ip4Header extends IpHeader {
+ private short checksum;
+
+ public Ip4Header(int proto, Inet4Address src, Inet4Address dst, Payload payload) {
+ super(proto, src, dst, payload);
+ }
+
+ public byte[] getPacketBytes() throws Exception {
+ ByteBuffer resultBuffer = buildHeader();
+ payload.addPacketBytes(this, resultBuffer);
+
+ return getByteArrayFromBuffer(resultBuffer);
+ }
+
+ public ByteBuffer buildHeader() {
+ ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN);
+
+ // Version, IHL
+ bb.put((byte) (0x45));
+
+ // DCSP, ECN
+ bb.put((byte) 0);
+
+ // Total Length
+ bb.putShort((short) (IP4_HDRLEN + payload.length()));
+
+ // Empty for Identification, Flags and Fragment Offset
+ bb.putShort((short) 0);
+ bb.put((byte) 0x40);
+ bb.put((byte) 0x00);
+
+ // TTL
+ bb.put((byte) 64);
+
+ // Protocol
+ bb.put(proto);
+
+ // Header Checksum
+ final int ipChecksumOffset = bb.position();
+ bb.putShort((short) 0);
+
+ // Src/Dst addresses
+ bb.put(srcAddr.getAddress());
+ bb.put(dstAddr.getAddress());
+
+ bb.putShort(ipChecksumOffset, calculateChecksum(bb));
+
+ return bb;
+ }
+
+ private short calculateChecksum(ByteBuffer bb) {
+ int checksum = 0;
+
+ // Calculate sum of 16-bit values, excluding checksum. IPv4 headers are always 32-bit
+ // aligned, so no special cases needed for unaligned values.
+ ShortBuffer shortBuffer = ByteBuffer.wrap(getByteArrayFromBuffer(bb)).asShortBuffer();
+ while (shortBuffer.hasRemaining()) {
+ short val = shortBuffer.get();
+
+ // Wrap as needed
+ checksum = addAndWrapForChecksum(checksum, val);
+ }
+
+ return onesComplement(checksum);
+ }
+
+ public int getProtocolId() {
+ return IPPROTO_IPV4;
+ }
+ }
+
+ public static class Ip6Header extends IpHeader {
+ public Ip6Header(int nextHeader, Inet6Address src, Inet6Address dst, Payload payload) {
+ super(nextHeader, src, dst, payload);
+ }
+
+ public byte[] getPacketBytes() throws Exception {
+ ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN);
+
+ // Version | Traffic Class (First 4 bits)
+ bb.put((byte) 0x60);
+
+ // Traffic class (Last 4 bits), Flow Label
+ bb.put((byte) 0);
+ bb.put((byte) 0);
+ bb.put((byte) 0);
+
+ // Payload Length
+ bb.putShort((short) payload.length());
+
+ // Next Header
+ bb.put(proto);
+
+ // Hop Limit
+ bb.put((byte) 64);
+
+ // Src/Dst addresses
+ bb.put(srcAddr.getAddress());
+ bb.put(dstAddr.getAddress());
+
+ // Payload
+ payload.addPacketBytes(this, bb);
+
+ return getByteArrayFromBuffer(bb);
+ }
+
+ public int getProtocolId() {
+ return IPPROTO_IPV6;
+ }
+ }
+
+ public static class BytePayload implements Payload {
+ public final byte[] payload;
+
+ public BytePayload(byte[] payload) {
+ this.payload = payload;
+ }
+
+ public int getProtocolId() {
+ return -1;
+ }
+
+ public byte[] getPacketBytes(IpHeader header) {
+ ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN);
+
+ addPacketBytes(header, bb);
+ return getByteArrayFromBuffer(bb);
+ }
+
+ public void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) {
+ resultBuffer.put(payload);
+ }
+
+ public short length() {
+ return (short) payload.length;
+ }
+ }
+
+ public static class UdpHeader implements Payload {
+
+ public final short srcPort;
+ public final short dstPort;
+ public final Payload payload;
+
+ public UdpHeader(int srcPort, int dstPort, Payload payload) {
+ this.srcPort = (short) srcPort;
+ this.dstPort = (short) dstPort;
+ this.payload = payload;
+ }
+
+ public int getProtocolId() {
+ return IPPROTO_UDP;
+ }
+
+ public short length() {
+ return (short) (payload.length() + 8);
+ }
+
+ public byte[] getPacketBytes(IpHeader header) throws Exception {
+ ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN);
+
+ addPacketBytes(header, bb);
+ return getByteArrayFromBuffer(bb);
+ }
+
+ public void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) throws Exception {
+ // Source, Destination port
+ resultBuffer.putShort(srcPort);
+ resultBuffer.putShort(dstPort);
+
+ // Payload Length
+ resultBuffer.putShort(length());
+
+ // Get payload bytes for checksum + payload
+ ByteBuffer payloadBuffer = ByteBuffer.allocate(DATA_BUFFER_LEN);
+ payload.addPacketBytes(header, payloadBuffer);
+ byte[] payloadBytes = getByteArrayFromBuffer(payloadBuffer);
+
+ // Checksum
+ resultBuffer.putShort(calculateChecksum(header, payloadBytes));
+
+ // Payload
+ resultBuffer.put(payloadBytes);
+ }
+
+ private short calculateChecksum(IpHeader header, byte[] payloadBytes) throws Exception {
+ int newChecksum = 0;
+ ShortBuffer srcBuffer = ByteBuffer.wrap(header.srcAddr.getAddress()).asShortBuffer();
+ ShortBuffer dstBuffer = ByteBuffer.wrap(header.dstAddr.getAddress()).asShortBuffer();
+
+ while (srcBuffer.hasRemaining() || dstBuffer.hasRemaining()) {
+ short val = srcBuffer.hasRemaining() ? srcBuffer.get() : dstBuffer.get();
+
+ // Wrap as needed
+ newChecksum = addAndWrapForChecksum(newChecksum, val);
+ }
+
+ // Add pseudo-header values. Proto is 0-padded, so just use the byte.
+ newChecksum = addAndWrapForChecksum(newChecksum, header.proto);
+ newChecksum = addAndWrapForChecksum(newChecksum, length());
+ newChecksum = addAndWrapForChecksum(newChecksum, srcPort);
+ newChecksum = addAndWrapForChecksum(newChecksum, dstPort);
+ newChecksum = addAndWrapForChecksum(newChecksum, length());
+
+ ShortBuffer payloadShortBuffer = ByteBuffer.wrap(payloadBytes).asShortBuffer();
+ while (payloadShortBuffer.hasRemaining()) {
+ newChecksum = addAndWrapForChecksum(newChecksum, payloadShortBuffer.get());
+ }
+ if (payload.length() % 2 != 0) {
+ newChecksum =
+ addAndWrapForChecksum(
+ newChecksum, (payloadBytes[payloadBytes.length - 1] << 8));
+ }
+
+ return onesComplement(newChecksum);
+ }
+ }
+
+ public static class EspHeader implements Payload {
+ public final int nextHeader;
+ public final int spi;
+ public final int seqNum;
+ public final byte[] key;
+ public final byte[] payload;
+
+ /**
+ * Generic constructor for ESP headers.
+ *
+ * <p>For Tunnel mode, payload will be a full IP header + attached payloads
+ *
+ * <p>For Transport mode, payload will be only the attached payloads, but with the checksum
+ * calculated using the pre-encryption IP header
+ */
+ public EspHeader(int nextHeader, int spi, int seqNum, byte[] key, byte[] payload) {
+ this.nextHeader = nextHeader;
+ this.spi = spi;
+ this.seqNum = seqNum;
+ this.key = key;
+ this.payload = payload;
+ }
+
+ public int getProtocolId() {
+ return IPPROTO_ESP;
+ }
+
+ public short length() {
+ // ALWAYS uses AES-CBC, HMAC-SHA256 (128b trunc len)
+ return (short)
+ calculateEspPacketSize(payload.length, AES_CBC_IV_LEN, AES_CBC_BLK_SIZE, 128);
+ }
+
+ public byte[] getPacketBytes(IpHeader header) throws Exception {
+ ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN);
+
+ addPacketBytes(header, bb);
+ return getByteArrayFromBuffer(bb);
+ }
+
+ public void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) throws Exception {
+ ByteBuffer espPayloadBuffer = ByteBuffer.allocate(DATA_BUFFER_LEN);
+ espPayloadBuffer.putInt(spi);
+ espPayloadBuffer.putInt(seqNum);
+ espPayloadBuffer.put(getCiphertext(key));
+
+ espPayloadBuffer.put(getIcv(getByteArrayFromBuffer(espPayloadBuffer)), 0, 16);
+ resultBuffer.put(getByteArrayFromBuffer(espPayloadBuffer));
+ }
+
+ private byte[] getIcv(byte[] authenticatedSection) throws GeneralSecurityException {
+ Mac sha256HMAC = Mac.getInstance(HMAC_SHA_256);
+ SecretKeySpec authKey = new SecretKeySpec(key, HMAC_SHA_256);
+ sha256HMAC.init(authKey);
+
+ return sha256HMAC.doFinal(authenticatedSection);
+ }
+
+ /**
+ * Encrypts and builds ciphertext block. Includes the IV, Padding and Next-Header blocks
+ *
+ * <p>The ciphertext does NOT include the SPI/Sequence numbers, or the ICV.
+ */
+ private byte[] getCiphertext(byte[] key) throws GeneralSecurityException {
+ int paddedLen = calculateEspEncryptedLength(payload.length, AES_CBC_BLK_SIZE);
+ ByteBuffer paddedPayload = ByteBuffer.allocate(paddedLen);
+ paddedPayload.put(payload);
+
+ // Add padding - consecutive integers from 0x01
+ int pad = 1;
+ while (paddedPayload.position() < paddedPayload.limit()) {
+ paddedPayload.put((byte) pad++);
+ }
+
+ paddedPayload.position(paddedPayload.limit() - 2);
+ paddedPayload.put((byte) (paddedLen - 2 - payload.length)); // Pad length
+ paddedPayload.put((byte) nextHeader);
+
+ // Generate Initialization Vector
+ byte[] iv = new byte[AES_CBC_IV_LEN];
+ new SecureRandom().nextBytes(iv);
+ IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
+ SecretKeySpec secretKeySpec = new SecretKeySpec(key, AES);
+
+ // Encrypt payload
+ Cipher cipher = Cipher.getInstance(AES_CBC);
+ cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
+ byte[] encrypted = cipher.doFinal(getByteArrayFromBuffer(paddedPayload));
+
+ // Build ciphertext
+ ByteBuffer cipherText = ByteBuffer.allocate(AES_CBC_IV_LEN + encrypted.length);
+ cipherText.put(iv);
+ cipherText.put(encrypted);
+
+ return getByteArrayFromBuffer(cipherText);
+ }
+ }
+
+ private static int addAndWrapForChecksum(int currentChecksum, int value) {
+ currentChecksum += value & 0x0000ffff;
+
+ // Wrap anything beyond the first 16 bits, and add to lower order bits
+ return (currentChecksum >>> 16) + (currentChecksum & 0x0000ffff);
+ }
+
+ private static short onesComplement(int val) {
+ val = (val >>> 16) + (val & 0xffff);
+
+ if (val == 0) return 0;
+ return (short) ((~val) & 0xffff);
+ }
+
+ public static int calculateEspPacketSize(
+ int payloadLen, int cryptIvLength, int cryptBlockSize, int authTruncLen) {
+ final int ESP_HDRLEN = 4 + 4; // SPI + Seq#
+ final int ICV_LEN = authTruncLen / 8; // Auth trailer; based on truncation length
+ payloadLen += cryptIvLength; // Initialization Vector
+
+ // Align to block size of encryption algorithm
+ payloadLen = calculateEspEncryptedLength(payloadLen, cryptBlockSize);
+ return payloadLen + ESP_HDRLEN + ICV_LEN;
+ }
+
+ private static int calculateEspEncryptedLength(int payloadLen, int cryptBlockSize) {
+ payloadLen += 2; // ESP trailer
+
+ // Align to block size of encryption algorithm
+ return payloadLen + calculateEspPadLen(payloadLen, cryptBlockSize);
+ }
+
+ private static int calculateEspPadLen(int payloadLen, int cryptBlockSize) {
+ return (cryptBlockSize - (payloadLen % cryptBlockSize)) % cryptBlockSize;
+ }
+
+ private static byte[] getByteArrayFromBuffer(ByteBuffer buffer) {
+ return Arrays.copyOfRange(buffer.array(), 0, buffer.position());
+ }
+
+ /*
+ * Debug printing
+ */
+ private static final char[] hexArray = "0123456789ABCDEF".toCharArray();
+
+ public static String bytesToHex(byte[] bytes) {
+ StringBuilder sb = new StringBuilder();
+ for (byte b : bytes) {
+ sb.append(hexArray[b >>> 4]);
+ sb.append(hexArray[b & 0x0F]);
+ sb.append(' ');
+ }
+ return sb.toString();
+ }
+}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/SaProposalTest.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/SaProposalTest.java
new file mode 100644
index 0000000..e0d3be0
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/SaProposalTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2020 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.ipsec.ike.cts;
+
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_1024_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_NONE;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_3DES;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_NONE;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_128;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_192;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_256;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_UNUSED;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_SHA2_256;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_SHA2_384;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_SHA2_512;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.util.Pair;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class SaProposalTest {
+ private static final List<Pair<Integer, Integer>> NORMAL_MODE_CIPHERS = new ArrayList<>();
+ private static final List<Pair<Integer, Integer>> COMBINED_MODE_CIPHERS = new ArrayList<>();
+ private static final List<Integer> INTEGRITY_ALGOS = new ArrayList<>();
+ private static final List<Integer> DH_GROUPS = new ArrayList<>();
+ private static final List<Integer> DH_GROUPS_WITH_NONE = new ArrayList<>();
+ private static final List<Integer> PRFS = new ArrayList<>();
+
+ static {
+ NORMAL_MODE_CIPHERS.add(new Pair<>(ENCRYPTION_ALGORITHM_3DES, KEY_LEN_UNUSED));
+ NORMAL_MODE_CIPHERS.add(new Pair<>(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128));
+ NORMAL_MODE_CIPHERS.add(new Pair<>(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192));
+ NORMAL_MODE_CIPHERS.add(new Pair<>(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256));
+
+ COMBINED_MODE_CIPHERS.add(new Pair<>(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128));
+ COMBINED_MODE_CIPHERS.add(new Pair<>(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192));
+ COMBINED_MODE_CIPHERS.add(new Pair<>(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256));
+
+ INTEGRITY_ALGOS.add(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+ INTEGRITY_ALGOS.add(INTEGRITY_ALGORITHM_AES_XCBC_96);
+ INTEGRITY_ALGOS.add(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128);
+ INTEGRITY_ALGOS.add(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192);
+ INTEGRITY_ALGOS.add(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256);
+
+ DH_GROUPS.add(DH_GROUP_1024_BIT_MODP);
+ DH_GROUPS.add(DH_GROUP_2048_BIT_MODP);
+
+ DH_GROUPS_WITH_NONE.add(DH_GROUP_NONE);
+ DH_GROUPS_WITH_NONE.addAll(DH_GROUPS);
+
+ PRFS.add(PSEUDORANDOM_FUNCTION_HMAC_SHA1);
+ PRFS.add(PSEUDORANDOM_FUNCTION_AES128_XCBC);
+ PRFS.add(PSEUDORANDOM_FUNCTION_SHA2_256);
+ PRFS.add(PSEUDORANDOM_FUNCTION_SHA2_384);
+ PRFS.add(PSEUDORANDOM_FUNCTION_SHA2_512);
+ }
+
+ // Package private
+ static IkeSaProposal buildIkeSaProposalWithNormalModeCipher() {
+ return buildIkeSaProposal(NORMAL_MODE_CIPHERS, INTEGRITY_ALGOS, PRFS, DH_GROUPS);
+ }
+
+ // Package private
+ static IkeSaProposal buildIkeSaProposalWithCombinedModeCipher() {
+ return buildIkeSaProposalWithCombinedModeCipher(true /* hasIntegrityNone */);
+ }
+
+ private static IkeSaProposal buildIkeSaProposalWithCombinedModeCipher(
+ boolean hasIntegrityNone) {
+ List<Integer> integerAlgos = new ArrayList<>();
+ if (hasIntegrityNone) {
+ integerAlgos.add(INTEGRITY_ALGORITHM_NONE);
+ }
+ return buildIkeSaProposal(COMBINED_MODE_CIPHERS, integerAlgos, PRFS, DH_GROUPS);
+ }
+
+ private static IkeSaProposal buildIkeSaProposal(
+ List<Pair<Integer, Integer>> ciphers,
+ List<Integer> integrityAlgos,
+ List<Integer> prfs,
+ List<Integer> dhGroups) {
+ IkeSaProposal.Builder builder = new IkeSaProposal.Builder();
+
+ for (Pair<Integer, Integer> pair : ciphers) {
+ builder.addEncryptionAlgorithm(pair.first, pair.second);
+ }
+ for (int algo : integrityAlgos) {
+ builder.addIntegrityAlgorithm(algo);
+ }
+ for (int algo : prfs) {
+ builder.addPseudorandomFunction(algo);
+ }
+ for (int algo : dhGroups) {
+ builder.addDhGroup(algo);
+ }
+
+ return builder.build();
+ }
+
+ // Package private
+ static ChildSaProposal buildChildSaProposalWithNormalModeCipher() {
+ return buildChildSaProposal(NORMAL_MODE_CIPHERS, INTEGRITY_ALGOS, DH_GROUPS_WITH_NONE);
+ }
+
+ // Package private
+ static ChildSaProposal buildChildSaProposalWithCombinedModeCipher() {
+ return buildChildSaProposalWithCombinedModeCipher(true /* hasIntegrityNone */);
+ }
+
+ private static ChildSaProposal buildChildSaProposalWithCombinedModeCipher(
+ boolean hasIntegrityNone) {
+ List<Integer> integerAlgos = new ArrayList<>();
+ if (hasIntegrityNone) {
+ integerAlgos.add(INTEGRITY_ALGORITHM_NONE);
+ }
+
+ return buildChildSaProposal(COMBINED_MODE_CIPHERS, integerAlgos, DH_GROUPS_WITH_NONE);
+ }
+
+ private static ChildSaProposal buildChildSaProposal(
+ List<Pair<Integer, Integer>> ciphers,
+ List<Integer> integrityAlgos,
+ List<Integer> dhGroups) {
+ ChildSaProposal.Builder builder = new ChildSaProposal.Builder();
+
+ for (Pair<Integer, Integer> pair : ciphers) {
+ builder.addEncryptionAlgorithm(pair.first, pair.second);
+ }
+ for (int algo : integrityAlgos) {
+ builder.addIntegrityAlgorithm(algo);
+ }
+ for (int algo : dhGroups) {
+ builder.addDhGroup(algo);
+ }
+
+ return builder.build();
+ }
+
+ // Package private
+ static ChildSaProposal buildChildSaProposalWithOnlyCiphers() {
+ return buildChildSaProposal(
+ COMBINED_MODE_CIPHERS, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+ }
+
+ @Test
+ public void testBuildIkeSaProposalWithNormalModeCipher() {
+ IkeSaProposal saProposal = buildIkeSaProposalWithNormalModeCipher();
+
+ assertEquals(NORMAL_MODE_CIPHERS, saProposal.getEncryptionAlgorithms());
+ assertEquals(INTEGRITY_ALGOS, saProposal.getIntegrityAlgorithms());
+ assertEquals(PRFS, saProposal.getPseudorandomFunctions());
+ assertEquals(DH_GROUPS, saProposal.getDhGroups());
+ }
+
+ @Test
+ public void testBuildIkeSaProposalWithCombinedModeCipher() {
+ IkeSaProposal saProposal =
+ buildIkeSaProposalWithCombinedModeCipher(false /* hasIntegrityNone */);
+
+ assertEquals(COMBINED_MODE_CIPHERS, saProposal.getEncryptionAlgorithms());
+ assertEquals(PRFS, saProposal.getPseudorandomFunctions());
+ assertEquals(DH_GROUPS, saProposal.getDhGroups());
+ assertTrue(saProposal.getIntegrityAlgorithms().isEmpty());
+ }
+
+ @Test
+ public void testBuildIkeSaProposalWithCombinedModeCipherAndIntegrityNone() {
+ IkeSaProposal saProposal =
+ buildIkeSaProposalWithCombinedModeCipher(true /* hasIntegrityNone */);
+
+ assertEquals(COMBINED_MODE_CIPHERS, saProposal.getEncryptionAlgorithms());
+ assertEquals(PRFS, saProposal.getPseudorandomFunctions());
+ assertEquals(DH_GROUPS, saProposal.getDhGroups());
+ assertEquals(Arrays.asList(INTEGRITY_ALGORITHM_NONE), saProposal.getIntegrityAlgorithms());
+ }
+
+ @Test
+ public void testBuildChildSaProposalWithNormalModeCipher() {
+ ChildSaProposal saProposal = buildChildSaProposalWithNormalModeCipher();
+
+ assertEquals(NORMAL_MODE_CIPHERS, saProposal.getEncryptionAlgorithms());
+ assertEquals(INTEGRITY_ALGOS, saProposal.getIntegrityAlgorithms());
+ assertEquals(DH_GROUPS_WITH_NONE, saProposal.getDhGroups());
+ }
+
+ @Test
+ public void testBuildChildProposalWithCombinedModeCipher() {
+ ChildSaProposal saProposal =
+ buildChildSaProposalWithCombinedModeCipher(false /* hasIntegrityNone */);
+
+ assertEquals(COMBINED_MODE_CIPHERS, saProposal.getEncryptionAlgorithms());
+ assertTrue(saProposal.getIntegrityAlgorithms().isEmpty());
+ assertEquals(DH_GROUPS_WITH_NONE, saProposal.getDhGroups());
+ }
+
+ @Test
+ public void testBuildChildProposalWithCombinedModeCipherAndIntegrityNone() {
+ ChildSaProposal saProposal =
+ buildChildSaProposalWithCombinedModeCipher(true /* hasIntegrityNone */);
+
+ assertEquals(COMBINED_MODE_CIPHERS, saProposal.getEncryptionAlgorithms());
+ assertEquals(Arrays.asList(INTEGRITY_ALGORITHM_NONE), saProposal.getIntegrityAlgorithms());
+ assertEquals(DH_GROUPS_WITH_NONE, saProposal.getDhGroups());
+ }
+
+ @Test
+ public void testBuildChildSaProposalWithOnlyCiphers() {
+ ChildSaProposal saProposal = buildChildSaProposalWithOnlyCiphers();
+
+ assertEquals(COMBINED_MODE_CIPHERS, saProposal.getEncryptionAlgorithms());
+ assertTrue(saProposal.getIntegrityAlgorithms().isEmpty());
+ assertTrue(saProposal.getDhGroups().isEmpty());
+ }
+
+ // TODO(b/148689509): Test throwing exception when algorithm combination is invalid
+}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TestNetworkUtils.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TestNetworkUtils.java
new file mode 100644
index 0000000..5b08cdc
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TestNetworkUtils.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 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.ipsec.ike.cts;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.TestNetworkManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+// TODO(b/148689509): Share this class with net CTS test (e.g. IpSecManagerTunnelTest)
+public class TestNetworkUtils {
+ private static final int TIMEOUT_MS = 500;
+
+ /** Callback to receive requested test network. */
+ public static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+ private final CompletableFuture<Network> futureNetwork = new CompletableFuture<>();
+
+ @Override
+ public void onAvailable(Network network) {
+ futureNetwork.complete(network);
+ }
+
+ public Network getNetworkBlocking() throws Exception {
+ return futureNetwork.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ /**
+ * Set up test network.
+ *
+ * <p>Caller MUST have MANAGE_TEST_NETWORKS permission to use this method.
+ *
+ * @param connMgr ConnectivityManager to request network.
+ * @param testNetworkMgr TestNetworkManager to set up test network.
+ * @param ifname the name of the interface to be used for the Network LinkProperties.
+ * @param binder a binder object guarding the lifecycle of this test network.
+ * @return TestNetworkCallback to retrieve the test network.
+ * @throws RemoteException if test network setup failed.
+ * @see android.net.TestNetworkManager
+ */
+ public static TestNetworkCallback setupAndGetTestNetwork(
+ ConnectivityManager connMgr,
+ TestNetworkManager testNetworkMgr,
+ String ifname,
+ IBinder binder)
+ throws RemoteException {
+ NetworkRequest nr =
+ new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .removeCapability(NET_CAPABILITY_NOT_VPN)
+ .setNetworkSpecifier(ifname)
+ .build();
+
+ TestNetworkCallback cb = new TestNetworkCallback();
+ connMgr.requestNetwork(nr, cb);
+
+ // Setup the test network after network request is filed to prevent Network from being
+ // reaped due to no requests matching it.
+ testNetworkMgr.setupTestNetwork(ifname, binder);
+
+ return cb;
+ }
+}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java
new file mode 100644
index 0000000..cb1d826
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2020 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.ipsec.ike.cts;
+
+import static android.net.ipsec.ike.cts.PacketUtils.IP4_HDRLEN;
+import static android.net.ipsec.ike.cts.PacketUtils.IP6_HDRLEN;
+import static android.net.ipsec.ike.cts.PacketUtils.IPPROTO_ESP;
+import static android.net.ipsec.ike.cts.PacketUtils.UDP_HDRLEN;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * This code is a exact copy of {@link TunUtils} in
+ * cts/tests/tests/net/src/android/net/cts/TunUtils.java, except the import path of PacketUtils is
+ * the path to the copy of PacktUtils.
+ *
+ * <p>TODO(b/148689509): Statically include the TunUtils source file instead of copying it.
+ */
+public class TunUtils {
+ private static final String TAG = TunUtils.class.getSimpleName();
+
+ private static final int DATA_BUFFER_LEN = 4096;
+ static final int TIMEOUT = 100;
+
+ static final int IP4_PROTO_OFFSET = 9;
+ static final int IP6_PROTO_OFFSET = 6;
+
+ static final int IP4_ADDR_OFFSET = 12;
+ static final int IP4_ADDR_LEN = 4;
+ static final int IP6_ADDR_OFFSET = 8;
+ static final int IP6_ADDR_LEN = 16;
+
+ final List<byte[]> mPackets = new ArrayList<>();
+ private final ParcelFileDescriptor mTunFd;
+ private final Thread mReaderThread;
+
+ public TunUtils(ParcelFileDescriptor tunFd) {
+ mTunFd = tunFd;
+
+ // Start background reader thread
+ mReaderThread =
+ new Thread(
+ () -> {
+ try {
+ // Loop will exit and thread will quit when tunFd is closed.
+ // Receiving either EOF or an exception will exit this reader loop.
+ // FileInputStream in uninterruptable, so there's no good way to
+ // ensure that this thread shuts down except upon FD closure.
+ while (true) {
+ byte[] intercepted = receiveFromTun();
+ if (intercepted == null) {
+ // Exit once we've hit EOF
+ return;
+ } else if (intercepted.length > 0) {
+ // Only save packet if we've received any bytes.
+ synchronized (mPackets) {
+ mPackets.add(intercepted);
+ mPackets.notifyAll();
+ }
+ }
+ }
+ } catch (IOException ignored) {
+ // Simply exit this reader thread
+ return;
+ }
+ });
+ mReaderThread.start();
+ }
+
+ private byte[] receiveFromTun() throws IOException {
+ FileInputStream in = new FileInputStream(mTunFd.getFileDescriptor());
+ byte[] inBytes = new byte[DATA_BUFFER_LEN];
+ int bytesRead = in.read(inBytes);
+
+ if (bytesRead < 0) {
+ return null; // return null for EOF
+ } else if (bytesRead >= DATA_BUFFER_LEN) {
+ throw new IllegalStateException("Too big packet. Fragmentation unsupported");
+ }
+ return Arrays.copyOf(inBytes, bytesRead);
+ }
+
+ byte[] getFirstMatchingPacket(Predicate<byte[]> verifier, int startIndex) {
+ synchronized (mPackets) {
+ for (int i = startIndex; i < mPackets.size(); i++) {
+ byte[] pkt = mPackets.get(i);
+ if (verifier.test(pkt)) {
+ return pkt;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Checks if the specified bytes were ever sent in plaintext.
+ *
+ * <p>Only checks for known plaintext bytes to prevent triggering on ICMP/RA packets or the like
+ *
+ * @param plaintext the plaintext bytes to check for
+ * @param startIndex the index in the list to check for
+ */
+ public boolean hasPlaintextPacket(byte[] plaintext, int startIndex) {
+ Predicate<byte[]> verifier =
+ (pkt) -> {
+ return Collections.indexOfSubList(Arrays.asList(pkt), Arrays.asList(plaintext))
+ != -1;
+ };
+ return getFirstMatchingPacket(verifier, startIndex) != null;
+ }
+
+ public byte[] getEspPacket(int spi, boolean encap, int startIndex) {
+ return getFirstMatchingPacket(
+ (pkt) -> {
+ return isEsp(pkt, spi, encap);
+ },
+ startIndex);
+ }
+
+ public byte[] awaitEspPacketNoPlaintext(
+ int spi, byte[] plaintext, boolean useEncap, int expectedPacketSize) throws Exception {
+ long endTime = System.currentTimeMillis() + TIMEOUT;
+ int startIndex = 0;
+
+ synchronized (mPackets) {
+ while (System.currentTimeMillis() < endTime) {
+ byte[] espPkt = getEspPacket(spi, useEncap, startIndex);
+ if (espPkt != null) {
+ // Validate packet size
+ assertEquals(expectedPacketSize, espPkt.length);
+
+ // Always check plaintext from start
+ assertFalse(hasPlaintextPacket(plaintext, 0));
+ return espPkt; // We've found the packet we're looking for.
+ }
+
+ startIndex = mPackets.size();
+
+ // Try to prevent waiting too long. If waitTimeout <= 0, we've already hit timeout
+ long waitTimeout = endTime - System.currentTimeMillis();
+ if (waitTimeout > 0) {
+ mPackets.wait(waitTimeout);
+ }
+ }
+
+ fail("No such ESP packet found with SPI " + spi);
+ }
+ return null;
+ }
+
+ private static boolean isSpiEqual(byte[] pkt, int espOffset, int spi) {
+ // Check SPI byte by byte.
+ return pkt[espOffset] == (byte) ((spi >>> 24) & 0xff)
+ && pkt[espOffset + 1] == (byte) ((spi >>> 16) & 0xff)
+ && pkt[espOffset + 2] == (byte) ((spi >>> 8) & 0xff)
+ && pkt[espOffset + 3] == (byte) (spi & 0xff);
+ }
+
+ private static boolean isEsp(byte[] pkt, int spi, boolean encap) {
+ if (isIpv6(pkt)) {
+ // IPv6 UDP encap not supported by kernels; assume non-encap.
+ return pkt[IP6_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP6_HDRLEN, spi);
+ } else {
+ // Use default IPv4 header length (assuming no options)
+ if (encap) {
+ return pkt[IP4_PROTO_OFFSET] == IPPROTO_UDP
+ && isSpiEqual(pkt, IP4_HDRLEN + UDP_HDRLEN, spi);
+ } else {
+ return pkt[IP4_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP4_HDRLEN, spi);
+ }
+ }
+ }
+
+ static boolean isIpv6(byte[] pkt) {
+ // First nibble shows IP version. 0x60 for IPv6
+ return (pkt[0] & (byte) 0xF0) == (byte) 0x60;
+ }
+
+ private static byte[] getReflectedPacket(byte[] pkt) {
+ byte[] reflected = Arrays.copyOf(pkt, pkt.length);
+
+ if (isIpv6(pkt)) {
+ // Set reflected packet's dst to that of the original's src
+ System.arraycopy(
+ pkt, // src
+ IP6_ADDR_OFFSET + IP6_ADDR_LEN, // src offset
+ reflected, // dst
+ IP6_ADDR_OFFSET, // dst offset
+ IP6_ADDR_LEN); // len
+ // Set reflected packet's src IP to that of the original's dst IP
+ System.arraycopy(
+ pkt, // src
+ IP6_ADDR_OFFSET, // src offset
+ reflected, // dst
+ IP6_ADDR_OFFSET + IP6_ADDR_LEN, // dst offset
+ IP6_ADDR_LEN); // len
+ } else {
+ // Set reflected packet's dst to that of the original's src
+ System.arraycopy(
+ pkt, // src
+ IP4_ADDR_OFFSET + IP4_ADDR_LEN, // src offset
+ reflected, // dst
+ IP4_ADDR_OFFSET, // dst offset
+ IP4_ADDR_LEN); // len
+ // Set reflected packet's src IP to that of the original's dst IP
+ System.arraycopy(
+ pkt, // src
+ IP4_ADDR_OFFSET, // src offset
+ reflected, // dst
+ IP4_ADDR_OFFSET + IP4_ADDR_LEN, // dst offset
+ IP4_ADDR_LEN); // len
+ }
+ return reflected;
+ }
+
+ /** Takes all captured packets, flips the src/dst, and re-injects them. */
+ public void reflectPackets() throws IOException {
+ synchronized (mPackets) {
+ for (byte[] pkt : mPackets) {
+ injectPacket(getReflectedPacket(pkt));
+ }
+ }
+ }
+
+ public void injectPacket(byte[] pkt) throws IOException {
+ FileOutputStream out = new FileOutputStream(mTunFd.getFileDescriptor());
+ out.write(pkt);
+ out.flush();
+ }
+
+ /** Resets the intercepted packets. */
+ public void reset() throws IOException {
+ synchronized (mPackets) {
+ mPackets.clear();
+ }
+ }
+}
diff --git a/tests/cts/net/jni/Android.bp b/tests/cts/net/jni/Android.bp
index baed48d..3953aeb 100644
--- a/tests/cts/net/jni/Android.bp
+++ b/tests/cts/net/jni/Android.bp
@@ -16,6 +16,7 @@
name: "libnativedns_jni",
srcs: ["NativeDnsJni.c"],
+ sdk_version: "current",
shared_libs: [
"libnativehelper_compat_libc++",
@@ -35,6 +36,7 @@
name: "libnativemultinetwork_jni",
srcs: ["NativeMultinetworkJni.cpp"],
+ sdk_version: "current",
cflags: [
"-Wall",
"-Werror",
diff --git a/tests/cts/net/jni/NativeDnsJni.c b/tests/cts/net/jni/NativeDnsJni.c
index 6d3d1c3..4ec800e 100644
--- a/tests/cts/net/jni/NativeDnsJni.c
+++ b/tests/cts/net/jni/NativeDnsJni.c
@@ -19,7 +19,12 @@
#include <netdb.h>
#include <stdio.h>
#include <string.h>
-#include <utils/Log.h>
+
+#include <android/log.h>
+
+#define LOG_TAG "NativeDns-JNI"
+#define LOGD(fmt, ...) \
+ __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##__VA_ARGS__)
const char *GoogleDNSIpV4Address="8.8.8.8";
const char *GoogleDNSIpV4Address2="8.8.4.4";
@@ -33,7 +38,7 @@
struct addrinfo *answer;
int res = getaddrinfo(node, service, NULL, &answer);
- ALOGD("getaddrinfo(www.google.com) gave res=%d (%s)", res, gai_strerror(res));
+ LOGD("getaddrinfo(www.google.com) gave res=%d (%s)", res, gai_strerror(res));
if (res != 0) return JNI_FALSE;
// check for v4 & v6
@@ -47,12 +52,12 @@
inet_ntop(current->ai_family, &((struct sockaddr_in *)current->ai_addr)->sin_addr,
buf, sizeof(buf));
foundv4 = 1;
- ALOGD(" %s", buf);
+ LOGD(" %s", buf);
} else if (current->ai_addr->sa_family == AF_INET6) {
inet_ntop(current->ai_family, &((struct sockaddr_in6 *)current->ai_addr)->sin6_addr,
buf, sizeof(buf));
foundv6 = 1;
- ALOGD(" %s", buf);
+ LOGD(" %s", buf);
}
current = current->ai_next;
}
@@ -60,14 +65,14 @@
freeaddrinfo(answer);
answer = NULL;
if (foundv4 != 1 && foundv6 != 1) {
- ALOGD("getaddrinfo(www.google.com) didn't find either v4 or v6 address");
+ LOGD("getaddrinfo(www.google.com) didn't find either v4 or v6 address");
return JNI_FALSE;
}
}
node = "ipv6.google.com";
res = getaddrinfo(node, service, NULL, &answer);
- ALOGD("getaddrinfo(ipv6.google.com) gave res=%d", res);
+ LOGD("getaddrinfo(ipv6.google.com) gave res=%d", res);
if (res != 0) return JNI_FALSE;
{
@@ -79,12 +84,12 @@
if (current->ai_addr->sa_family == AF_INET) {
inet_ntop(current->ai_family, &((struct sockaddr_in *)current->ai_addr)->sin_addr,
buf, sizeof(buf));
- ALOGD(" %s", buf);
+ LOGD(" %s", buf);
foundv4 = 1;
} else if (current->ai_addr->sa_family == AF_INET6) {
inet_ntop(current->ai_family, &((struct sockaddr_in6 *)current->ai_addr)->sin6_addr,
buf, sizeof(buf));
- ALOGD(" %s", buf);
+ LOGD(" %s", buf);
foundv6 = 1;
}
current = current->ai_next;
@@ -93,7 +98,7 @@
freeaddrinfo(answer);
answer = NULL;
if (foundv4 == 1 || foundv6 != 1) {
- ALOGD("getaddrinfo(ipv6.google.com) didn't find only v6");
+ LOGD("getaddrinfo(ipv6.google.com) didn't find only v6");
return JNI_FALSE;
}
}
@@ -116,12 +121,12 @@
res = getnameinfo((const struct sockaddr*)&sa4, sizeof(sa4), buf, sizeof(buf), NULL, 0, flags);
if (res != 0) {
- ALOGD("getnameinfo(%s (GoogleDNS) ) gave error %d (%s)", GoogleDNSIpV4Address, res,
+ LOGD("getnameinfo(%s (GoogleDNS) ) gave error %d (%s)", GoogleDNSIpV4Address, res,
gai_strerror(res));
return JNI_FALSE;
}
if (strstr(buf, "google.com") == NULL && strstr(buf, "dns.google") == NULL) {
- ALOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
+ LOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
GoogleDNSIpV4Address, buf);
return JNI_FALSE;
}
@@ -129,12 +134,12 @@
memset(buf, 0, sizeof(buf));
res = getnameinfo((const struct sockaddr*)&sa6, sizeof(sa6), buf, sizeof(buf), NULL, 0, flags);
if (res != 0) {
- ALOGD("getnameinfo(%s (GoogleDNS) ) gave error %d (%s)", GoogleDNSIpV6Address2,
+ LOGD("getnameinfo(%s (GoogleDNS) ) gave error %d (%s)", GoogleDNSIpV6Address2,
res, gai_strerror(res));
return JNI_FALSE;
}
if (strstr(buf, "google.com") == NULL && strstr(buf, "dns.google") == NULL) {
- ALOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
+ LOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
GoogleDNSIpV6Address2, buf);
return JNI_FALSE;
}
@@ -142,11 +147,11 @@
// gethostbyname
struct hostent *my_hostent = gethostbyname("www.youtube.com");
if (my_hostent == NULL) {
- ALOGD("gethostbyname(www.youtube.com) gave null response");
+ LOGD("gethostbyname(www.youtube.com) gave null response");
return JNI_FALSE;
}
if ((my_hostent->h_addr_list == NULL) || (*my_hostent->h_addr_list == NULL)) {
- ALOGD("gethostbyname(www.youtube.com) gave 0 addresses");
+ LOGD("gethostbyname(www.youtube.com) gave 0 addresses");
return JNI_FALSE;
}
{
@@ -154,7 +159,7 @@
while (*current != NULL) {
char buf[256];
inet_ntop(my_hostent->h_addrtype, *current, buf, sizeof(buf));
- ALOGD("gethostbyname(www.youtube.com) gave %s", buf);
+ LOGD("gethostbyname(www.youtube.com) gave %s", buf);
current++;
}
}
@@ -164,11 +169,11 @@
inet_pton(AF_INET6, GoogleDNSIpV6Address, addr6);
my_hostent = gethostbyaddr(addr6, sizeof(addr6), AF_INET6);
if (my_hostent == NULL) {
- ALOGD("gethostbyaddr(%s (GoogleDNS) ) gave null response", GoogleDNSIpV6Address);
+ LOGD("gethostbyaddr(%s (GoogleDNS) ) gave null response", GoogleDNSIpV6Address);
return JNI_FALSE;
}
- ALOGD("gethostbyaddr(%s (GoogleDNS) ) gave %s for name", GoogleDNSIpV6Address,
+ LOGD("gethostbyaddr(%s (GoogleDNS) ) gave %s for name", GoogleDNSIpV6Address,
my_hostent->h_name ? my_hostent->h_name : "null");
if (my_hostent->h_name == NULL) return JNI_FALSE;
diff --git a/tests/cts/net/jni/NativeMultinetworkJni.cpp b/tests/cts/net/jni/NativeMultinetworkJni.cpp
index 2832c3d..cd94709 100644
--- a/tests/cts/net/jni/NativeMultinetworkJni.cpp
+++ b/tests/cts/net/jni/NativeMultinetworkJni.cpp
@@ -16,7 +16,6 @@
#define LOG_TAG "MultinetworkApiTest"
-#include <utils/Log.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
@@ -34,9 +33,13 @@
#include <string>
+#include <android/log.h>
#include <android/multinetwork.h>
#include <nativehelper/JNIHelp.h>
+#define LOGD(fmt, ...) \
+ __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##__VA_ARGS__)
+
#define EXPECT_GE(env, actual, expected, msg) \
do { \
if (actual < expected) { \
@@ -138,7 +141,7 @@
uint8_t buf[MAXPACKET] = {};
int res = getAsyncResponse(env, fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
if (res != expectedErrno) {
- ALOGD("res:%d, expectedErrno = %d", res, expectedErrno);
+ LOGD("res:%d, expectedErrno = %d", res, expectedErrno);
return (res > 0) ? -EREMOTEIO : res;
}
return 0;
@@ -326,7 +329,7 @@
const int saved_errno = errno;
freeaddrinfo(res);
- ALOGD("android_getaddrinfofornetwork(%" PRIu64 ", %s) returned rval=%d errno=%d",
+ LOGD("android_getaddrinfofornetwork(%" PRIu64 ", %s) returned rval=%d errno=%d",
handle, kHostname, rval, saved_errno);
return rval == 0 ? 0 : -saved_errno;
}
@@ -339,7 +342,7 @@
errno = 0;
int rval = android_setprocnetwork(handle);
const int saved_errno = errno;
- ALOGD("android_setprocnetwork(%" PRIu64 ") returned rval=%d errno=%d",
+ LOGD("android_setprocnetwork(%" PRIu64 ") returned rval=%d errno=%d",
handle, rval, saved_errno);
return rval == 0 ? 0 : -saved_errno;
}
@@ -352,14 +355,14 @@
errno = 0;
int fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
if (fd < 0) {
- ALOGD("socket() failed, errno=%d", errno);
+ LOGD("socket() failed, errno=%d", errno);
return -errno;
}
errno = 0;
int rval = android_setsocknetwork(handle, fd);
const int saved_errno = errno;
- ALOGD("android_setprocnetwork(%" PRIu64 ", %d) returned rval=%d errno=%d",
+ LOGD("android_setprocnetwork(%" PRIu64 ", %d) returned rval=%d errno=%d",
handle, fd, rval, saved_errno);
close(fd);
return rval == 0 ? 0 : -saved_errno;
@@ -404,7 +407,7 @@
static const char kPort[] = "443";
int rval = android_getaddrinfofornetwork(handle, kHostname, kPort, &kHints, &res);
if (rval != 0) {
- ALOGD("android_getaddrinfofornetwork(%llu, %s) returned rval=%d errno=%d",
+ LOGD("android_getaddrinfofornetwork(%llu, %s) returned rval=%d errno=%d",
handle, kHostname, rval, errno);
freeaddrinfo(res);
return -errno;
@@ -413,14 +416,14 @@
// Rely upon getaddrinfo sorting the best destination to the front.
int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (fd < 0) {
- ALOGD("socket(%d, %d, %d) failed, errno=%d",
+ LOGD("socket(%d, %d, %d) failed, errno=%d",
res->ai_family, res->ai_socktype, res->ai_protocol, errno);
freeaddrinfo(res);
return -errno;
}
rval = android_setsocknetwork(handle, fd);
- ALOGD("android_setprocnetwork(%llu, %d) returned rval=%d errno=%d",
+ LOGD("android_setprocnetwork(%llu, %d) returned rval=%d errno=%d",
handle, fd, rval, errno);
if (rval != 0) {
close(fd);
@@ -430,7 +433,7 @@
char addrstr[kSockaddrStrLen+1];
sockaddr_ntop(res->ai_addr, res->ai_addrlen, addrstr, sizeof(addrstr));
- ALOGD("Attempting connect() to %s ...", addrstr);
+ LOGD("Attempting connect() to %s ...", addrstr);
rval = connect(fd, res->ai_addr, res->ai_addrlen);
if (rval != 0) {
@@ -447,7 +450,7 @@
return -errno;
}
sockaddr_ntop((const struct sockaddr *)&src_addr, sizeof(src_addr), addrstr, sizeof(addrstr));
- ALOGD("... from %s", addrstr);
+ LOGD("... from %s", addrstr);
// Don't let reads or writes block indefinitely.
const struct timeval timeo = { 2, 0 }; // 2 seconds
@@ -479,7 +482,7 @@
sent = send(fd, quic_packet, sizeof(quic_packet), 0);
if (sent < (ssize_t)sizeof(quic_packet)) {
errnum = errno;
- ALOGD("send(QUIC packet) returned sent=%zd, errno=%d", sent, errnum);
+ LOGD("send(QUIC packet) returned sent=%zd, errno=%d", sent, errnum);
close(fd);
return -errnum;
}
@@ -489,14 +492,14 @@
break;
} else {
errnum = errno;
- ALOGD("[%d/%d] recv(QUIC response) returned rcvd=%zd, errno=%d",
+ LOGD("[%d/%d] recv(QUIC response) returned rcvd=%zd, errno=%d",
i + 1, MAX_RETRIES, rcvd, errnum);
}
}
if (rcvd < 9) {
- ALOGD("QUIC UDP %s: sent=%zd but rcvd=%zd, errno=%d", kPort, sent, rcvd, errnum);
+ LOGD("QUIC UDP %s: sent=%zd but rcvd=%zd, errno=%d", kPort, sent, rcvd, errnum);
if (rcvd <= 0) {
- ALOGD("Does this network block UDP port %s?", kPort);
+ LOGD("Does this network block UDP port %s?", kPort);
}
close(fd);
return -EPROTO;
@@ -504,7 +507,7 @@
int conn_id_cmp = memcmp(quic_packet + 1, response + 1, 8);
if (conn_id_cmp != 0) {
- ALOGD("sent and received connection IDs do not match");
+ LOGD("sent and received connection IDs do not match");
close(fd);
return -EPROTO;
}
diff --git a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
new file mode 100644
index 0000000..4418e17
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2020 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.cts
+
+import android.Manifest.permission.NETWORK_SETTINGS
+import android.Manifest.permission.READ_DEVICE_CONFIG
+import android.Manifest.permission.WRITE_DEVICE_CONFIG
+import android.content.pm.PackageManager.FEATURE_TELEPHONY
+import android.content.pm.PackageManager.FEATURE_WIFI
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.Uri
+import android.net.cts.util.CtsNetUtils
+import android.net.wifi.WifiManager
+import android.os.ConditionVariable
+import android.platform.test.annotations.AppModeFull
+import android.provider.DeviceConfig
+import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
+import android.text.TextUtils
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.SystemUtil
+import fi.iki.elonen.NanoHTTPD
+import fi.iki.elonen.NanoHTTPD.Response.IStatus
+import fi.iki.elonen.NanoHTTPD.Response.Status
+import junit.framework.AssertionFailedError
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.runner.RunWith
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+import kotlin.test.Test
+import kotlin.test.assertNotEquals
+import kotlin.test.assertTrue
+
+private const val TEST_CAPTIVE_PORTAL_HTTPS_URL_SETTING = "test_captive_portal_https_url"
+private const val TEST_CAPTIVE_PORTAL_HTTP_URL_SETTING = "test_captive_portal_http_url"
+private const val TEST_URL_EXPIRATION_TIME = "test_url_expiration_time"
+
+private const val TEST_HTTPS_URL_PATH = "https_path"
+private const val TEST_HTTP_URL_PATH = "http_path"
+private const val TEST_PORTAL_URL_PATH = "portal_path"
+
+private const val LOCALHOST_HOSTNAME = "localhost"
+
+// Re-connecting to the AP, obtaining an IP address, revalidating can take a long time
+private const val WIFI_CONNECT_TIMEOUT_MS = 120_000L
+private const val TEST_TIMEOUT_MS = 10_000L
+
+private fun <T> CompletableFuture<T>.assertGet(timeoutMs: Long, message: String): T {
+ try {
+ return get(timeoutMs, TimeUnit.MILLISECONDS)
+ } catch (e: TimeoutException) {
+ throw AssertionFailedError(message)
+ }
+}
+
+@AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
+@RunWith(AndroidJUnit4::class)
+class CaptivePortalTest {
+ private val context: android.content.Context by lazy { getInstrumentation().context }
+ private val wm by lazy { context.getSystemService(WifiManager::class.java) }
+ private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
+ private val pm by lazy { context.packageManager }
+ private val utils by lazy { CtsNetUtils(context) }
+
+ private val server = HttpServer()
+
+ @Before
+ fun setUp() {
+ doAsShell(READ_DEVICE_CONFIG) {
+ // Verify that the test URLs are not normally set on the device, but do not fail if the
+ // test URLs are set to what this test uses (URLs on localhost), in case the test was
+ // interrupted manually and rerun.
+ assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL_SETTING)
+ assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTP_URL_SETTING)
+ }
+ clearTestUrls()
+ server.start()
+ }
+
+ @After
+ fun tearDown() {
+ clearTestUrls()
+ server.stop()
+ }
+
+ private fun assertEmptyOrLocalhostUrl(urlKey: String) {
+ val url = DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, urlKey)
+ assertTrue(TextUtils.isEmpty(url) || LOCALHOST_HOSTNAME == Uri.parse(url).host,
+ "$urlKey must not be set in production scenarios (current value: $url)")
+ }
+
+ private fun clearTestUrls() {
+ setHttpsUrl(null)
+ setHttpUrl(null)
+ setUrlExpiration(null)
+ }
+
+ @Test
+ fun testCaptivePortalIsNotDefaultNetwork() {
+ assumeTrue(pm.hasSystemFeature(FEATURE_TELEPHONY))
+ assumeTrue(pm.hasSystemFeature(FEATURE_WIFI))
+ utils.connectToWifi()
+ utils.connectToCell()
+
+ // Have network validation use a local server that serves a HTTPS error / HTTP redirect
+ server.addResponse(TEST_PORTAL_URL_PATH, Status.OK,
+ content = "Test captive portal content")
+ server.addResponse(TEST_HTTPS_URL_PATH, Status.INTERNAL_ERROR)
+ server.addResponse(TEST_HTTP_URL_PATH, Status.REDIRECT,
+ locationHeader = server.makeUrl(TEST_PORTAL_URL_PATH))
+ setHttpsUrl(server.makeUrl(TEST_HTTPS_URL_PATH))
+ setHttpUrl(server.makeUrl(TEST_HTTP_URL_PATH))
+ // URL expiration needs to be in the next 10 minutes
+ setUrlExpiration(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(9))
+
+ // Expect the portal content to be fetched at some point after detecting the portal.
+ // Some implementations may fetch the URL before startCaptivePortalApp is called.
+ val portalContentRequestCv = server.addExpectRequestCv(TEST_PORTAL_URL_PATH)
+
+ // Wait for a captive portal to be detected on the network
+ val wifiNetworkFuture = CompletableFuture<Network>()
+ val wifiCb = object : NetworkCallback() {
+ override fun onCapabilitiesChanged(
+ network: Network,
+ nc: NetworkCapabilities
+ ) {
+ if (nc.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
+ wifiNetworkFuture.complete(network)
+ }
+ }
+ }
+ cm.requestNetwork(NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(), wifiCb)
+
+ try {
+ reconnectWifi()
+ val network = wifiNetworkFuture.assertGet(WIFI_CONNECT_TIMEOUT_MS,
+ "Captive portal not detected after ${WIFI_CONNECT_TIMEOUT_MS}ms")
+
+ val wifiDefaultMessage = "Wifi should not be the default network when a captive " +
+ "portal was detected and another network (mobile data) can provide internet " +
+ "access."
+ assertNotEquals(network, cm.activeNetwork, wifiDefaultMessage)
+
+ doAsShell(NETWORK_SETTINGS) { cm.startCaptivePortalApp(network) }
+ assertTrue(portalContentRequestCv.block(TEST_TIMEOUT_MS), "The captive portal login " +
+ "page was still not fetched ${TEST_TIMEOUT_MS}ms after startCaptivePortalApp.")
+
+ assertNotEquals(network, cm.activeNetwork, wifiDefaultMessage)
+ } finally {
+ cm.unregisterNetworkCallback(wifiCb)
+ server.stop()
+ // disconnectFromCell should be called after connectToCell
+ utils.disconnectFromCell()
+ }
+
+ clearTestUrls()
+ reconnectWifi()
+ }
+
+ private fun setHttpsUrl(url: String?) = setConfig(TEST_CAPTIVE_PORTAL_HTTPS_URL_SETTING, url)
+ private fun setHttpUrl(url: String?) = setConfig(TEST_CAPTIVE_PORTAL_HTTP_URL_SETTING, url)
+ private fun setUrlExpiration(timestamp: Long?) = setConfig(TEST_URL_EXPIRATION_TIME,
+ timestamp?.toString())
+
+ private fun setConfig(configKey: String, value: String?) {
+ doAsShell(WRITE_DEVICE_CONFIG) {
+ DeviceConfig.setProperty(
+ NAMESPACE_CONNECTIVITY, configKey, value, false /* makeDefault */)
+ }
+ }
+
+ private fun doAsShell(vararg permissions: String, action: () -> Unit) {
+ // Wrap the below call to allow for more kotlin-like syntax
+ SystemUtil.runWithShellPermissionIdentity(action, permissions)
+ }
+
+ private fun reconnectWifi() {
+ doAsShell(NETWORK_SETTINGS) {
+ assertTrue(wm.disconnect())
+ assertTrue(wm.reconnect())
+ }
+ }
+
+ /**
+ * A minimal HTTP server running on localhost (loopback), on a random available port.
+ */
+ private class HttpServer : NanoHTTPD("localhost", 0 /* auto-select the port */) {
+ // Map of URL path -> HTTP response code
+ private val responses = HashMap<String, Response>()
+
+ // Map of path -> CV to open as soon as a request to the path is received
+ private val waitForRequestCv = HashMap<String, ConditionVariable>()
+
+ /**
+ * Create a URL string that, when fetched, will hit this server with the given URL [path].
+ */
+ fun makeUrl(path: String): String {
+ return Uri.Builder()
+ .scheme("http")
+ .encodedAuthority("localhost:$listeningPort")
+ .query(path)
+ .build()
+ .toString()
+ }
+
+ fun addResponse(
+ path: String,
+ statusCode: IStatus,
+ locationHeader: String? = null,
+ content: String = ""
+ ) {
+ val response = newFixedLengthResponse(statusCode, "text/plain", content)
+ locationHeader?.let { response.addHeader("Location", it) }
+ responses[path] = response
+ }
+
+ /**
+ * Create a [ConditionVariable] that will open when a request to [path] is received.
+ */
+ fun addExpectRequestCv(path: String): ConditionVariable {
+ return ConditionVariable().apply { waitForRequestCv[path] = this }
+ }
+
+ override fun serve(session: IHTTPSession): Response {
+ waitForRequestCv[session.queryParameterString]?.open()
+ return responses[session.queryParameterString]
+ // Default response is a 404
+ ?: super.serve(session)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
index 0a80047..9d35705 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
@@ -18,15 +18,17 @@
import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import android.content.Context;
import android.net.ConnectivityDiagnosticsManager;
import android.net.NetworkRequest;
+import android.os.Build;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
@@ -34,7 +36,8 @@
import java.util.concurrent.Executor;
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@IgnoreUpTo(Build.VERSION_CODES.Q) // ConnectivityDiagnosticsManager did not exist in Q
public class ConnectivityDiagnosticsManagerTest {
private static final Executor INLINE_EXECUTOR = x -> x.run();
private static final NetworkRequest DEFAULT_REQUEST = new NetworkRequest.Builder().build();
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index fa7e138..1ee08ff 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -639,11 +639,14 @@
}
}
- private void waitForActiveNetworkMetered(boolean requestedMeteredness) throws Exception {
+ private void waitForActiveNetworkMetered(int targetTransportType, boolean requestedMeteredness)
+ throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final NetworkCallback networkCallback = new NetworkCallback() {
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+ if (!nc.hasTransport(targetTransportType)) return;
+
final boolean metered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED);
if (metered == requestedMeteredness) {
latch.countDown();
@@ -709,7 +712,7 @@
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public void testGetMultipathPreference() throws Exception {
final ContentResolver resolver = mContext.getContentResolver();
- final Network network = ensureWifiConnected();
+ ensureWifiConnected();
final String ssid = unquoteSSID(mWifiManager.getConnectionInfo().getSSID());
final String oldMeteredSetting = getWifiMeteredStatus(ssid);
final String oldMeteredMultipathPreference = Settings.Global.getString(
@@ -720,7 +723,11 @@
Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
Integer.toString(newMeteredPreference));
setWifiMeteredStatus(ssid, "true");
- waitForActiveNetworkMetered(true);
+ waitForActiveNetworkMetered(TRANSPORT_WIFI, true);
+ // Wifi meterness changes from unmetered to metered will disconnect and reconnect since
+ // R.
+ final Network network = ensureWifiConnected();
+ assertEquals(ssid, unquoteSSID(mWifiManager.getConnectionInfo().getSSID()));
assertEquals(mCm.getNetworkCapabilities(network).hasCapability(
NET_CAPABILITY_NOT_METERED), false);
assertMultipathPreferenceIsEventually(network, initialMeteredPreference,
@@ -736,7 +743,8 @@
oldMeteredPreference, newMeteredPreference);
setWifiMeteredStatus(ssid, "false");
- waitForActiveNetworkMetered(false);
+ // No disconnect from unmetered to metered.
+ waitForActiveNetworkMetered(TRANSPORT_WIFI, false);
assertEquals(mCm.getNetworkCapabilities(network).hasCapability(
NET_CAPABILITY_NOT_METERED), true);
assertMultipathPreferenceIsEventually(network, newMeteredPreference,
diff --git a/tests/cts/net/src/android/net/cts/DhcpInfoTest.java b/tests/cts/net/src/android/net/cts/DhcpInfoTest.java
deleted file mode 100644
index 085fdd9..0000000
--- a/tests/cts/net/src/android/net/cts/DhcpInfoTest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2009 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.cts;
-
-import android.net.DhcpInfo;
-import android.test.AndroidTestCase;
-
-public class DhcpInfoTest extends AndroidTestCase {
-
- public void testConstructor() {
- new DhcpInfo();
- }
-
- public void testToString() {
- String expectedDefault = "ipaddr 0.0.0.0 gateway 0.0.0.0 netmask 0.0.0.0 dns1 0.0.0.0 "
- + "dns2 0.0.0.0 DHCP server 0.0.0.0 lease 0 seconds";
- String STR_ADDR1 = "255.255.255.255";
- String STR_ADDR2 = "127.0.0.1";
- String STR_ADDR3 = "192.168.1.1";
- String STR_ADDR4 = "192.168.1.0";
- int leaseTime = 9999;
- String expected = "ipaddr " + STR_ADDR1 + " gateway " + STR_ADDR2 + " netmask "
- + STR_ADDR3 + " dns1 " + STR_ADDR4 + " dns2 " + STR_ADDR4 + " DHCP server "
- + STR_ADDR2 + " lease " + leaseTime + " seconds";
-
- DhcpInfo dhcpInfo = new DhcpInfo();
-
- // Test default string.
- assertEquals(expectedDefault, dhcpInfo.toString());
-
- dhcpInfo.ipAddress = ipToInteger(STR_ADDR1);
- dhcpInfo.gateway = ipToInteger(STR_ADDR2);
- dhcpInfo.netmask = ipToInteger(STR_ADDR3);
- dhcpInfo.dns1 = ipToInteger(STR_ADDR4);
- dhcpInfo.dns2 = ipToInteger(STR_ADDR4);
- dhcpInfo.serverAddress = ipToInteger(STR_ADDR2);
- dhcpInfo.leaseDuration = leaseTime;
-
- // Test with new values
- assertEquals(expected, dhcpInfo.toString());
- }
-
- private int ipToInteger(String ipString) {
- String ipSegs[] = ipString.split("[.]");
- int tmp = Integer.parseInt(ipSegs[3]) << 24 | Integer.parseInt(ipSegs[2]) << 16 |
- Integer.parseInt(ipSegs[1]) << 8 | Integer.parseInt(ipSegs[0]);
- return tmp;
- }
-}
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
index 999d2f1..1d83dda 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
@@ -192,9 +192,8 @@
// Build a network request
NetworkRequest nr =
new NetworkRequest.Builder()
+ .clearCapabilities()
.addTransportType(TRANSPORT_TEST)
- .removeCapability(NET_CAPABILITY_TRUSTED)
- .removeCapability(NET_CAPABILITY_NOT_VPN)
.setNetworkSpecifier(ifname)
.build();
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
new file mode 100644
index 0000000..03b961b
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -0,0 +1,595 @@
+/*
+ * Copyright (C) 2020 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.cts
+
+import android.app.Instrumentation
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.KeepalivePacketData
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.Network
+import android.net.NetworkAgent
+import android.net.NetworkAgent.CMD_ADD_KEEPALIVE_PACKET_FILTER
+import android.net.NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT
+import android.net.NetworkAgent.CMD_REMOVE_KEEPALIVE_PACKET_FILTER
+import android.net.NetworkAgent.CMD_REPORT_NETWORK_STATUS
+import android.net.NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED
+import android.net.NetworkAgent.CMD_START_SOCKET_KEEPALIVE
+import android.net.NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE
+import android.net.NetworkAgent.INVALID_NETWORK
+import android.net.NetworkAgent.VALID_NETWORK
+import android.net.NetworkAgentConfig
+import android.net.NetworkCapabilities
+import android.net.NetworkProvider
+import android.net.NetworkRequest
+import android.net.SocketKeepalive
+import android.net.StringNetworkSpecifier
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+import android.os.Message
+import android.os.Messenger
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnAddKeepalivePacketFilter
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnAutomaticReconnectDisabled
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnBandwidthUpdateRequested
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkUnwanted
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnRemoveKeepalivePacketFilter
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnSaveAcceptUnvalidated
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnSignalStrengthThresholdsUpdated
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnStartSocketKeepalive
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnStopSocketKeepalive
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnValidationStatus
+import androidx.test.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.AsyncChannel
+import com.android.testutils.ArrayTrackRecord
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
+import com.android.testutils.TestableNetworkCallback
+import java.util.UUID
+import org.junit.After
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.net.InetAddress
+import java.time.Duration
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertFailsWith
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+// This test doesn't really have a constraint on how fast the methods should return. If it's
+// going to fail, it will simply wait forever, so setting a high timeout lowers the flake ratio
+// without affecting the run time of successful runs. Thus, set a very high timeout.
+private const val DEFAULT_TIMEOUT_MS = 5000L
+// When waiting for a NetworkCallback to determine there was no timeout, waiting is the
+// only possible thing (the relevant handler is the one in the real ConnectivityService,
+// and then there is the Binder call), so have a short timeout for this as it will be
+// exhausted every time.
+private const val NO_CALLBACK_TIMEOUT = 200L
+// Any legal score (0~99) for the test network would do, as it is going to be kept up by the
+// requests filed by the test and should never match normal internet requests. 70 is the default
+// score of Ethernet networks, it's as good a value as any other.
+private const val TEST_NETWORK_SCORE = 70
+private const val BETTER_NETWORK_SCORE = 75
+private const val FAKE_NET_ID = 1098
+private val instrumentation: Instrumentation
+ get() = InstrumentationRegistry.getInstrumentation()
+private val context: Context
+ get() = InstrumentationRegistry.getContext()
+private fun Message(what: Int, arg1: Int, arg2: Int, obj: Any?) = Message.obtain().also {
+ it.what = what
+ it.arg1 = arg1
+ it.arg2 = arg2
+ it.obj = obj
+}
+
+@RunWith(AndroidJUnit4::class)
+class NetworkAgentTest {
+ @Rule @JvmField
+ val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
+
+ private val LOCAL_IPV4_ADDRESS = InetAddress.parseNumericAddress("192.0.2.1")
+ private val REMOTE_IPV4_ADDRESS = InetAddress.parseNumericAddress("192.0.2.2")
+
+ private val mCM = context.getSystemService(ConnectivityManager::class.java)
+ private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread")
+ private val mFakeConnectivityService by lazy { FakeConnectivityService(mHandlerThread.looper) }
+
+ private class Provider(context: Context, looper: Looper) :
+ NetworkProvider(context, looper, "NetworkAgentTest NetworkProvider")
+
+ private val agentsToCleanUp = mutableListOf<NetworkAgent>()
+ private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>()
+
+ @Before
+ fun setUp() {
+ instrumentation.getUiAutomation().adoptShellPermissionIdentity()
+ mHandlerThread.start()
+ }
+
+ @After
+ fun tearDown() {
+ agentsToCleanUp.forEach { it.unregister() }
+ callbacksToCleanUp.forEach { mCM.unregisterNetworkCallback(it) }
+ mHandlerThread.quitSafely()
+ instrumentation.getUiAutomation().dropShellPermissionIdentity()
+ }
+
+ /**
+ * A fake that helps simulating ConnectivityService talking to a harnessed agent.
+ * This fake only supports speaking to one harnessed agent at a time because it
+ * only keeps track of one async channel.
+ */
+ private class FakeConnectivityService(looper: Looper) {
+ private val CMD_EXPECT_DISCONNECT = 1
+ private var disconnectExpected = false
+ private val msgHistory = ArrayTrackRecord<Message>().newReadHead()
+ private val asyncChannel = AsyncChannel()
+ private val handler = object : Handler(looper) {
+ override fun handleMessage(msg: Message) {
+ msgHistory.add(Message.obtain(msg)) // make a copy as the original will be recycled
+ when (msg.what) {
+ CMD_EXPECT_DISCONNECT -> disconnectExpected = true
+ AsyncChannel.CMD_CHANNEL_HALF_CONNECTED ->
+ asyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION)
+ AsyncChannel.CMD_CHANNEL_DISCONNECTED ->
+ if (!disconnectExpected) {
+ fail("Agent unexpectedly disconnected")
+ } else {
+ disconnectExpected = false
+ }
+ }
+ }
+ }
+
+ fun connect(agentMsngr: Messenger) = asyncChannel.connect(context, handler, agentMsngr)
+
+ fun disconnect() = asyncChannel.disconnect()
+
+ fun sendMessage(what: Int, arg1: Int = 0, arg2: Int = 0, obj: Any? = null) =
+ asyncChannel.sendMessage(Message(what, arg1, arg2, obj))
+
+ fun expectMessage(what: Int) =
+ assertNotNull(msgHistory.poll(DEFAULT_TIMEOUT_MS) { it.what == what })
+
+ fun willExpectDisconnectOnce() = handler.sendEmptyMessage(CMD_EXPECT_DISCONNECT)
+ }
+
+ private open class TestableNetworkAgent(
+ looper: Looper,
+ val nc: NetworkCapabilities,
+ val lp: LinkProperties,
+ conf: NetworkAgentConfig
+ ) : NetworkAgent(context, looper, TestableNetworkAgent::class.java.simpleName /* tag */,
+ nc, lp, TEST_NETWORK_SCORE, conf, Provider(context, looper)) {
+ private val history = ArrayTrackRecord<CallbackEntry>().newReadHead()
+
+ sealed class CallbackEntry {
+ object OnBandwidthUpdateRequested : CallbackEntry()
+ object OnNetworkUnwanted : CallbackEntry()
+ data class OnAddKeepalivePacketFilter(
+ val slot: Int,
+ val packet: KeepalivePacketData
+ ) : CallbackEntry()
+ data class OnRemoveKeepalivePacketFilter(val slot: Int) : CallbackEntry()
+ data class OnStartSocketKeepalive(
+ val slot: Int,
+ val interval: Int,
+ val packet: KeepalivePacketData
+ ) : CallbackEntry()
+ data class OnStopSocketKeepalive(val slot: Int) : CallbackEntry()
+ data class OnSaveAcceptUnvalidated(val accept: Boolean) : CallbackEntry()
+ object OnAutomaticReconnectDisabled : CallbackEntry()
+ data class OnValidationStatus(val status: Int, val uri: Uri?) : CallbackEntry()
+ data class OnSignalStrengthThresholdsUpdated(val thresholds: IntArray) : CallbackEntry()
+ }
+
+ fun getName(): String? = (nc.getNetworkSpecifier() as? StringNetworkSpecifier)?.specifier
+
+ override fun onBandwidthUpdateRequested() {
+ history.add(OnBandwidthUpdateRequested)
+ }
+
+ override fun onNetworkUnwanted() {
+ history.add(OnNetworkUnwanted)
+ }
+
+ override fun onAddKeepalivePacketFilter(slot: Int, packet: KeepalivePacketData) {
+ history.add(OnAddKeepalivePacketFilter(slot, packet))
+ }
+
+ override fun onRemoveKeepalivePacketFilter(slot: Int) {
+ history.add(OnRemoveKeepalivePacketFilter(slot))
+ }
+
+ override fun onStartSocketKeepalive(
+ slot: Int,
+ interval: Duration,
+ packet: KeepalivePacketData
+ ) {
+ history.add(OnStartSocketKeepalive(slot, interval.seconds.toInt(), packet))
+ }
+
+ override fun onStopSocketKeepalive(slot: Int) {
+ history.add(OnStopSocketKeepalive(slot))
+ }
+
+ override fun onSaveAcceptUnvalidated(accept: Boolean) {
+ history.add(OnSaveAcceptUnvalidated(accept))
+ }
+
+ override fun onAutomaticReconnectDisabled() {
+ history.add(OnAutomaticReconnectDisabled)
+ }
+
+ override fun onSignalStrengthThresholdsUpdated(thresholds: IntArray) {
+ history.add(OnSignalStrengthThresholdsUpdated(thresholds))
+ }
+
+ fun expectEmptySignalStrengths() {
+ expectCallback<OnSignalStrengthThresholdsUpdated>().let {
+ // intArrayOf() without arguments makes an empty array
+ assertArrayEquals(intArrayOf(), it.thresholds)
+ }
+ }
+
+ override fun onValidationStatus(status: Int, uri: Uri?) {
+ history.add(OnValidationStatus(status, uri))
+ }
+
+ // Expects the initial validation event that always occurs immediately after registering
+ // a NetworkAgent whose network does not require validation (which test networks do
+ // not, since they lack the INTERNET capability). It always contains the default argument
+ // for the URI.
+ fun expectNoInternetValidationStatus() = expectCallback<OnValidationStatus>().let {
+ assertEquals(it.status, VALID_NETWORK)
+ // The returned Uri is parsed from the empty string, which means it's an
+ // instance of the (private) Uri.StringUri. There are no real good ways
+ // to check this, the least bad is to just convert it to a string and
+ // make sure it's empty.
+ assertEquals("", it.uri.toString())
+ }
+
+ inline fun <reified T : CallbackEntry> expectCallback(): T {
+ val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
+ assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
+ return foundCallback
+ }
+
+ fun assertNoCallback() {
+ assertTrue(waitForIdle(DEFAULT_TIMEOUT_MS),
+ "Handler didn't became idle after ${DEFAULT_TIMEOUT_MS}ms")
+ assertNull(history.peek())
+ }
+ }
+
+ private fun requestNetwork(request: NetworkRequest, callback: TestableNetworkCallback) {
+ mCM.requestNetwork(request, callback)
+ callbacksToCleanUp.add(callback)
+ }
+
+ private fun registerNetworkCallback(
+ request: NetworkRequest,
+ callback: TestableNetworkCallback
+ ) {
+ mCM.registerNetworkCallback(request, callback)
+ callbacksToCleanUp.add(callback)
+ }
+
+ private fun createNetworkAgent(name: String? = null): TestableNetworkAgent {
+ val nc = NetworkCapabilities().apply {
+ addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+ addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+ addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+ if (null != name) {
+ setNetworkSpecifier(StringNetworkSpecifier(name))
+ }
+ }
+ val lp = LinkProperties().apply {
+ addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 0))
+ }
+ val config = NetworkAgentConfig.Builder().build()
+ return TestableNetworkAgent(mHandlerThread.looper, nc, lp, config).also {
+ agentsToCleanUp.add(it)
+ }
+ }
+
+ private fun createConnectedNetworkAgent(name: String? = null):
+ Pair<TestableNetworkAgent, TestableNetworkCallback> {
+ val request: NetworkRequest = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .build()
+ val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
+ requestNetwork(request, callback)
+ val agent = createNetworkAgent(name)
+ agent.register()
+ agent.markConnected()
+ return agent to callback
+ }
+
+ private fun createNetworkAgentWithFakeCS() = createNetworkAgent().also {
+ mFakeConnectivityService.connect(it.registerForTest(Network(FAKE_NET_ID)))
+ }
+
+ @Test
+ fun testConnectAndUnregister() {
+ val (agent, callback) = createConnectedNetworkAgent()
+ callback.expectAvailableThenValidatedCallbacks(agent.network)
+ agent.expectEmptySignalStrengths()
+ agent.expectNoInternetValidationStatus()
+ agent.unregister()
+ callback.expectCallback<Lost>(agent.network)
+ agent.expectCallback<OnNetworkUnwanted>()
+ assertFailsWith<IllegalStateException>("Must not be able to register an agent twice") {
+ agent.register()
+ }
+ }
+
+ @Test
+ fun testOnBandwidthUpdateRequested() {
+ val (agent, callback) = createConnectedNetworkAgent()
+ callback.expectAvailableThenValidatedCallbacks(agent.network)
+ agent.expectEmptySignalStrengths()
+ agent.expectNoInternetValidationStatus()
+ mCM.requestBandwidthUpdate(agent.network)
+ agent.expectCallback<OnBandwidthUpdateRequested>()
+ agent.unregister()
+ }
+
+ @Test
+ fun testSignalStrengthThresholds() {
+ val thresholds = intArrayOf(30, 50, 65)
+ val callbacks = thresholds.map { strength ->
+ val request = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .setSignalStrength(strength)
+ .build()
+ TestableNetworkCallback(DEFAULT_TIMEOUT_MS).also {
+ registerNetworkCallback(request, it)
+ }
+ }
+ createConnectedNetworkAgent().let { (agent, callback) ->
+ callback.expectAvailableThenValidatedCallbacks(agent.network)
+ agent.expectCallback<OnSignalStrengthThresholdsUpdated>().let {
+ assertArrayEquals(it.thresholds, thresholds)
+ }
+ agent.expectNoInternetValidationStatus()
+
+ // Send signal strength and check that the callbacks are called appropriately.
+ val nc = NetworkCapabilities(agent.nc)
+ nc.setSignalStrength(20)
+ agent.sendNetworkCapabilities(nc)
+ callbacks.forEach { it.assertNoCallback(NO_CALLBACK_TIMEOUT) }
+
+ nc.setSignalStrength(40)
+ agent.sendNetworkCapabilities(nc)
+ callbacks[0].expectAvailableCallbacks(agent.network)
+ callbacks[1].assertNoCallback(NO_CALLBACK_TIMEOUT)
+ callbacks[2].assertNoCallback(NO_CALLBACK_TIMEOUT)
+
+ nc.setSignalStrength(80)
+ agent.sendNetworkCapabilities(nc)
+ callbacks[0].expectCapabilitiesThat(agent.network) { it.signalStrength == 80 }
+ callbacks[1].expectAvailableCallbacks(agent.network)
+ callbacks[2].expectAvailableCallbacks(agent.network)
+
+ nc.setSignalStrength(55)
+ agent.sendNetworkCapabilities(nc)
+ callbacks[0].expectCapabilitiesThat(agent.network) { it.signalStrength == 55 }
+ callbacks[1].expectCapabilitiesThat(agent.network) { it.signalStrength == 55 }
+ callbacks[2].expectCallback<Lost>(agent.network)
+ }
+ callbacks.forEach {
+ mCM.unregisterNetworkCallback(it)
+ }
+ }
+
+ @Test
+ fun testSocketKeepalive(): Unit = createNetworkAgentWithFakeCS().let { agent ->
+ val packet = object : KeepalivePacketData(
+ LOCAL_IPV4_ADDRESS /* srcAddress */, 1234 /* srcPort */,
+ REMOTE_IPV4_ADDRESS /* dstAddress */, 4567 /* dstPort */,
+ ByteArray(100 /* size */) { it.toByte() /* init */ }) {}
+ val slot = 4
+ val interval = 37
+
+ mFakeConnectivityService.sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER,
+ arg1 = slot, obj = packet)
+ mFakeConnectivityService.sendMessage(CMD_START_SOCKET_KEEPALIVE,
+ arg1 = slot, arg2 = interval, obj = packet)
+
+ agent.expectCallback<OnAddKeepalivePacketFilter>().let {
+ assertEquals(it.slot, slot)
+ assertEquals(it.packet, packet)
+ }
+ agent.expectCallback<OnStartSocketKeepalive>().let {
+ assertEquals(it.slot, slot)
+ assertEquals(it.interval, interval)
+ assertEquals(it.packet, packet)
+ }
+
+ agent.assertNoCallback()
+
+ // Check that when the agent sends a keepalive event, ConnectivityService receives the
+ // expected message.
+ agent.sendSocketKeepaliveEvent(slot, SocketKeepalive.ERROR_UNSUPPORTED)
+ mFakeConnectivityService.expectMessage(NetworkAgent.EVENT_SOCKET_KEEPALIVE).let() {
+ assertEquals(slot, it.arg1)
+ assertEquals(SocketKeepalive.ERROR_UNSUPPORTED, it.arg2)
+ }
+
+ mFakeConnectivityService.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, arg1 = slot)
+ mFakeConnectivityService.sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, arg1 = slot)
+ agent.expectCallback<OnStopSocketKeepalive>().let {
+ assertEquals(it.slot, slot)
+ }
+ agent.expectCallback<OnRemoveKeepalivePacketFilter>().let {
+ assertEquals(it.slot, slot)
+ }
+ }
+
+ @Test
+ fun testSendUpdates(): Unit = createConnectedNetworkAgent().let { (agent, callback) ->
+ callback.expectAvailableThenValidatedCallbacks(agent.network)
+ agent.expectEmptySignalStrengths()
+ agent.expectNoInternetValidationStatus()
+ val ifaceName = "adhocIface"
+ val lp = LinkProperties(agent.lp)
+ lp.setInterfaceName(ifaceName)
+ agent.sendLinkProperties(lp)
+ callback.expectLinkPropertiesThat(agent.network) {
+ it.getInterfaceName() == ifaceName
+ }
+ val nc = NetworkCapabilities(agent.nc)
+ nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+ agent.sendNetworkCapabilities(nc)
+ callback.expectCapabilitiesThat(agent.network) {
+ it.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+ }
+ }
+
+ @Test
+ fun testSendScore() {
+ // This test will create two networks and check that the one with the stronger
+ // score wins out for a request that matches them both.
+ // First create requests to make sure both networks are kept up, using the
+ // specifier so they are specific to each network
+ val name1 = UUID.randomUUID().toString()
+ val name2 = UUID.randomUUID().toString()
+ val request1 = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .setNetworkSpecifier(StringNetworkSpecifier(name1))
+ .build()
+ val request2 = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .setNetworkSpecifier(StringNetworkSpecifier(name2))
+ .build()
+ val callback1 = TestableNetworkCallback()
+ val callback2 = TestableNetworkCallback()
+ requestNetwork(request1, callback1)
+ requestNetwork(request2, callback2)
+
+ // Then file the interesting request
+ val request = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .build()
+ val callback = TestableNetworkCallback()
+ requestNetwork(request, callback)
+
+ // Connect the first Network
+ createConnectedNetworkAgent(name1).let { (agent1, _) ->
+ callback.expectAvailableThenValidatedCallbacks(agent1.network)
+ // Upgrade agent1 to a better score so that there is no ambiguity when
+ // agent2 connects that agent1 is still better
+ agent1.sendNetworkScore(BETTER_NETWORK_SCORE - 1)
+ // Connect the second agent
+ createConnectedNetworkAgent(name2).let { (agent2, _) ->
+ agent2.markConnected()
+ // The callback should not see anything yet
+ callback.assertNoCallback(NO_CALLBACK_TIMEOUT)
+ // Now update the score and expect the callback now prefers agent2
+ agent2.sendNetworkScore(BETTER_NETWORK_SCORE)
+ callback.expectCallback<Available>(agent2.network)
+ }
+ }
+
+ // tearDown() will unregister the requests and agents
+ }
+
+ @Test
+ fun testSetAcceptUnvalidated() {
+ createNetworkAgentWithFakeCS().let { agent ->
+ mFakeConnectivityService.sendMessage(CMD_SAVE_ACCEPT_UNVALIDATED, 1)
+ agent.expectCallback<OnSaveAcceptUnvalidated>().let {
+ assertTrue(it.accept)
+ }
+ agent.assertNoCallback()
+ }
+ }
+
+ @Test
+ fun testSetAcceptUnvalidatedPreventAutomaticReconnect() {
+ createNetworkAgentWithFakeCS().let { agent ->
+ mFakeConnectivityService.sendMessage(CMD_SAVE_ACCEPT_UNVALIDATED, 0)
+ mFakeConnectivityService.sendMessage(CMD_PREVENT_AUTOMATIC_RECONNECT)
+ agent.expectCallback<OnSaveAcceptUnvalidated>().let {
+ assertFalse(it.accept)
+ }
+ agent.expectCallback<OnAutomaticReconnectDisabled>()
+ agent.assertNoCallback()
+ // When automatic reconnect is turned off, the network is torn down and
+ // ConnectivityService sends a disconnect. This in turn causes the agent
+ // to send a DISCONNECTED message to CS.
+ mFakeConnectivityService.willExpectDisconnectOnce()
+ mFakeConnectivityService.disconnect()
+ mFakeConnectivityService.expectMessage(AsyncChannel.CMD_CHANNEL_DISCONNECTED)
+ agent.expectCallback<OnNetworkUnwanted>()
+ }
+ }
+
+ @Test
+ fun testPreventAutomaticReconnect() {
+ createNetworkAgentWithFakeCS().let { agent ->
+ mFakeConnectivityService.sendMessage(CMD_PREVENT_AUTOMATIC_RECONNECT)
+ agent.expectCallback<OnAutomaticReconnectDisabled>()
+ agent.assertNoCallback()
+ mFakeConnectivityService.willExpectDisconnectOnce()
+ mFakeConnectivityService.disconnect()
+ mFakeConnectivityService.expectMessage(AsyncChannel.CMD_CHANNEL_DISCONNECTED)
+ agent.expectCallback<OnNetworkUnwanted>()
+ }
+ }
+
+ @Test
+ fun testValidationStatus() = createNetworkAgentWithFakeCS().let { agent ->
+ val uri = Uri.parse("http://www.google.com")
+ val bundle = Bundle().apply {
+ putString(NetworkAgent.REDIRECT_URL_KEY, uri.toString())
+ }
+ mFakeConnectivityService.sendMessage(CMD_REPORT_NETWORK_STATUS,
+ arg1 = VALID_NETWORK, obj = bundle)
+ agent.expectCallback<OnValidationStatus>().let {
+ assertEquals(it.status, VALID_NETWORK)
+ assertEquals(it.uri, uri)
+ }
+
+ mFakeConnectivityService.sendMessage(CMD_REPORT_NETWORK_STATUS,
+ arg1 = INVALID_NETWORK, obj = Bundle())
+ agent.expectCallback<OnValidationStatus>().let {
+ assertEquals(it.status, INVALID_NETWORK)
+ assertNull(it.uri)
+ }
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index c862c77..5e92b41 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -16,24 +16,181 @@
package android.net.cts;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.MacAddress;
+import android.net.MatchAllNetworkSpecifier;
+import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
-import android.test.AndroidTestCase;
+import android.net.NetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.wifi.WifiNetworkSpecifier;
+import android.os.Build;
+import android.os.Process;
+import android.os.PatternMatcher;
-public class NetworkRequestTest extends AndroidTestCase {
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class NetworkRequestTest {
+ @Rule
+ public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
+ private static final String TEST_SSID = "TestSSID";
+ private static final String OTHER_SSID = "OtherSSID";
+ private static final int TEST_UID = 2097;
+ private static final String TEST_PACKAGE_NAME = "test.package.name";
+ private static final MacAddress ARBITRARY_ADDRESS = MacAddress.fromString("3:5:8:12:9:2");
+
+ @Test
public void testCapabilities() {
assertTrue(new NetworkRequest.Builder().addCapability(NET_CAPABILITY_MMS).build()
.hasCapability(NET_CAPABILITY_MMS));
assertFalse(new NetworkRequest.Builder().removeCapability(NET_CAPABILITY_MMS).build()
.hasCapability(NET_CAPABILITY_MMS));
+
+ final NetworkRequest nr = new NetworkRequest.Builder().clearCapabilities().build();
+ // Verify request has no capabilities
+ verifyNoCapabilities(nr);
}
+ private void verifyNoCapabilities(NetworkRequest nr) {
+ // NetworkCapabilities.mNetworkCapabilities is defined as type long
+ final int MAX_POSSIBLE_CAPABILITY = Long.SIZE;
+ for(int bit = 0; bit < MAX_POSSIBLE_CAPABILITY; bit++) {
+ assertFalse(nr.hasCapability(bit));
+ }
+ }
+
+ @Test
public void testTransports() {
assertTrue(new NetworkRequest.Builder().addTransportType(TRANSPORT_BLUETOOTH).build()
.hasTransport(TRANSPORT_BLUETOOTH));
assertFalse(new NetworkRequest.Builder().removeTransportType(TRANSPORT_BLUETOOTH).build()
.hasTransport(TRANSPORT_BLUETOOTH));
}
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.Q)
+ public void testSpecifier() {
+ assertNull(new NetworkRequest.Builder().build().getNetworkSpecifier());
+ final WifiNetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
+ .setSsidPattern(new PatternMatcher(TEST_SSID, PatternMatcher.PATTERN_LITERAL))
+ .setBssidPattern(ARBITRARY_ADDRESS, ARBITRARY_ADDRESS)
+ .build();
+ final NetworkSpecifier obtainedSpecifier = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .setNetworkSpecifier(specifier)
+ .build()
+ .getNetworkSpecifier();
+ assertEquals(obtainedSpecifier, specifier);
+
+ assertNull(new NetworkRequest.Builder()
+ .clearCapabilities()
+ .build()
+ .getNetworkSpecifier());
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.Q)
+ public void testRequestorPackageName() {
+ assertNull(new NetworkRequest.Builder().build().getRequestorPackageName());
+ final String pkgName = "android.net.test";
+ final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+ .setRequestorPackageName(pkgName)
+ .build();
+ final NetworkRequest nr = new NetworkRequest.Builder()
+ .setCapabilities(nc)
+ .build();
+ assertEquals(pkgName, nr.getRequestorPackageName());
+ assertNull(new NetworkRequest.Builder()
+ .clearCapabilities()
+ .build()
+ .getRequestorPackageName());
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.Q)
+ public void testCanBeSatisfiedBy() {
+ final TelephonyNetworkSpecifier specifier1 = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(1234 /* subId */)
+ .build();
+ final TelephonyNetworkSpecifier specifier2 = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(5678 /* subId */)
+ .build();
+ final NetworkCapabilities cap = new NetworkCapabilities()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_MMS)
+ .addCapability(NET_CAPABILITY_INTERNET);
+ final NetworkCapabilities capDualTransport = new NetworkCapabilities(cap)
+ .addTransportType(TRANSPORT_VPN);
+ final NetworkCapabilities capWithSpecifier1 =
+ new NetworkCapabilities(cap).setNetworkSpecifier(specifier1);
+ final NetworkCapabilities capDiffTransportWithSpecifier1 = new NetworkCapabilities()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addTransportType(TRANSPORT_VPN)
+ .setNetworkSpecifier(specifier1);
+
+ final NetworkRequest requestWithSpecifier1 = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .setNetworkSpecifier(specifier1)
+ .build();
+ assertFalse(requestWithSpecifier1.canBeSatisfiedBy(null));
+ assertFalse(requestWithSpecifier1.canBeSatisfiedBy(new NetworkCapabilities()));
+ assertTrue(requestWithSpecifier1.canBeSatisfiedBy(new NetworkCapabilities(cap)
+ .setNetworkSpecifier(new MatchAllNetworkSpecifier())));
+ assertTrue(requestWithSpecifier1.canBeSatisfiedBy(cap));
+ assertTrue(requestWithSpecifier1.canBeSatisfiedBy(capWithSpecifier1));
+ assertTrue(requestWithSpecifier1.canBeSatisfiedBy(capDualTransport));
+ assertFalse(requestWithSpecifier1.canBeSatisfiedBy(
+ new NetworkCapabilities(cap).setNetworkSpecifier(specifier2)));
+
+ final NetworkRequest request = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build();
+ assertTrue(request.canBeSatisfiedBy(cap));
+ assertTrue(request.canBeSatisfiedBy(capWithSpecifier1));
+ assertTrue(request.canBeSatisfiedBy(
+ new NetworkCapabilities(cap).setNetworkSpecifier(specifier2)));
+ assertFalse(request.canBeSatisfiedBy(capDiffTransportWithSpecifier1));
+ assertTrue(request.canBeSatisfiedBy(capDualTransport));
+
+ assertEquals(requestWithSpecifier1.canBeSatisfiedBy(capWithSpecifier1),
+ new NetworkCapabilities(capWithSpecifier1)
+ .satisfiedByNetworkCapabilities(capWithSpecifier1));
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ public void testRequestorUid() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ // Verify default value is INVALID_UID
+ assertEquals(Process.INVALID_UID, new NetworkRequest.Builder()
+ .setCapabilities(nc).build().getRequestorUid());
+
+ nc.setRequestorUid(1314);
+ final NetworkRequest nr = new NetworkRequest.Builder().setCapabilities(nc).build();
+ assertEquals(1314, nr.getRequestorUid());
+
+ assertEquals(Process.INVALID_UID, new NetworkRequest.Builder()
+ .clearCapabilities().build().getRequestorUid());
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsBinderTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsBinderTest.java
new file mode 100644
index 0000000..1f3162f
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsBinderTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2020 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.cts;
+
+import static android.os.Process.INVALID_UID;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.INetworkStatsService;
+import android.net.TrafficStats;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.test.AndroidTestCase;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.util.CollectionUtils;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class NetworkStatsBinderTest extends AndroidTestCase {
+ // NOTE: These are shamelessly copied from TrafficStats.
+ private static final int TYPE_RX_BYTES = 0;
+ private static final int TYPE_RX_PACKETS = 1;
+ private static final int TYPE_TX_BYTES = 2;
+ private static final int TYPE_TX_PACKETS = 3;
+
+ private final SparseArray<Function<Integer, Long>> mUidStatsQueryOpArray = new SparseArray<>();
+
+ @Override
+ protected void setUp() throws Exception {
+ mUidStatsQueryOpArray.put(TYPE_RX_BYTES, uid -> TrafficStats.getUidRxBytes(uid));
+ mUidStatsQueryOpArray.put(TYPE_RX_PACKETS, uid -> TrafficStats.getUidRxPackets(uid));
+ mUidStatsQueryOpArray.put(TYPE_TX_BYTES, uid -> TrafficStats.getUidTxBytes(uid));
+ mUidStatsQueryOpArray.put(TYPE_TX_PACKETS, uid -> TrafficStats.getUidTxPackets(uid));
+ }
+
+ private long getUidStatsFromBinder(int uid, int type) throws Exception {
+ Method getServiceMethod = Class.forName("android.os.ServiceManager")
+ .getDeclaredMethod("getService", new Class[]{String.class});
+ IBinder binder = (IBinder) getServiceMethod.invoke(null, Context.NETWORK_STATS_SERVICE);
+ INetworkStatsService nss = INetworkStatsService.Stub.asInterface(binder);
+ return nss.getUidStats(uid, type);
+ }
+
+ private int getFirstAppUidThat(@NonNull Predicate<Integer> predicate) {
+ PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+ List<PackageInfo> apps = pm.getInstalledPackages(0 /* flags */);
+ final PackageInfo match = CollectionUtils.find(apps,
+ it -> it.applicationInfo != null && predicate.test(it.applicationInfo.uid));
+ if (match != null) return match.applicationInfo.uid;
+ return INVALID_UID;
+ }
+
+ public void testAccessUidStatsFromBinder() throws Exception {
+ final int myUid = Process.myUid();
+ final List<Integer> testUidList = new ArrayList<>();
+
+ // Prepare uid list for testing.
+ testUidList.add(INVALID_UID);
+ testUidList.add(Process.ROOT_UID);
+ testUidList.add(Process.SYSTEM_UID);
+ testUidList.add(myUid);
+ testUidList.add(Process.LAST_APPLICATION_UID);
+ testUidList.add(Process.LAST_APPLICATION_UID + 1);
+ // If available, pick another existing uid for testing that is not already contained
+ // in the list above.
+ final int notMyUid = getFirstAppUidThat(uid -> uid >= 0 && !testUidList.contains(uid));
+ if (notMyUid != INVALID_UID) testUidList.add(notMyUid);
+
+ for (final int uid : testUidList) {
+ for (int i = 0; i < mUidStatsQueryOpArray.size(); i++) {
+ final int type = mUidStatsQueryOpArray.keyAt(i);
+ try {
+ final long uidStatsFromBinder = getUidStatsFromBinder(uid, type);
+ final long uidTrafficStats = mUidStatsQueryOpArray.get(type).apply(uid);
+
+ // Verify that UNSUPPORTED is returned if the uid is not current app uid.
+ if (uid != myUid) {
+ assertEquals(uidStatsFromBinder, TrafficStats.UNSUPPORTED);
+ }
+ // Verify that returned result is the same with the result get from
+ // TrafficStats.
+ // TODO: If the test is flaky then it should instead assert that the values
+ // are approximately similar.
+ assertEquals("uidStats is not matched for query type " + type
+ + ", uid=" + uid + ", myUid=" + myUid, uidTrafficStats,
+ uidStatsFromBinder);
+ } catch (IllegalAccessException e) {
+ /* Java language access prevents exploitation. */
+ return;
+ } catch (InvocationTargetException e) {
+ /* Underlying method has been changed. */
+ return;
+ } catch (ClassNotFoundException e) {
+ /* not vulnerable if hidden API no longer available */
+ return;
+ } catch (NoSuchMethodException e) {
+ /* not vulnerable if hidden API no longer available */
+ return;
+ } catch (RemoteException e) {
+ return;
+ }
+ }
+ }
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/ProxyInfoTest.java b/tests/cts/net/src/android/net/cts/ProxyInfoTest.java
new file mode 100644
index 0000000..1c5624c
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/ProxyInfoTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.ProxyInfo;
+import android.net.Uri;
+import android.os.Build;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+@RunWith(AndroidJUnit4.class)
+public final class ProxyInfoTest {
+ private static final String TEST_HOST = "test.example.com";
+ private static final int TEST_PORT = 5566;
+ private static final Uri TEST_URI = Uri.parse("https://test.example.com");
+ // This matches android.net.ProxyInfo#LOCAL_EXCL_LIST
+ private static final String LOCAL_EXCL_LIST = "";
+ // This matches android.net.ProxyInfo#LOCAL_HOST
+ private static final String LOCAL_HOST = "localhost";
+ // This matches android.net.ProxyInfo#LOCAL_PORT
+ private static final int LOCAL_PORT = -1;
+
+ @Rule
+ public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
+ @Test
+ public void testConstructor() {
+ final ProxyInfo proxy = new ProxyInfo((ProxyInfo) null);
+ checkEmpty(proxy);
+
+ assertEquals(proxy, new ProxyInfo(proxy));
+ }
+
+ @Test
+ public void testBuildDirectProxy() {
+ final ProxyInfo proxy1 = ProxyInfo.buildDirectProxy(TEST_HOST, TEST_PORT);
+
+ assertEquals(TEST_HOST, proxy1.getHost());
+ assertEquals(TEST_PORT, proxy1.getPort());
+ assertArrayEquals(new String[0], proxy1.getExclusionList());
+ assertEquals(Uri.EMPTY, proxy1.getPacFileUrl());
+
+ final List<String> exclList = new ArrayList<>();
+ exclList.add("localhost");
+ exclList.add("*.exclusion.com");
+ final ProxyInfo proxy2 = ProxyInfo.buildDirectProxy(TEST_HOST, TEST_PORT, exclList);
+
+ assertEquals(TEST_HOST, proxy2.getHost());
+ assertEquals(TEST_PORT, proxy2.getPort());
+ assertArrayEquals(exclList.toArray(new String[0]), proxy2.getExclusionList());
+ assertEquals(Uri.EMPTY, proxy2.getPacFileUrl());
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ public void testBuildPacProxy() {
+ final ProxyInfo proxy1 = ProxyInfo.buildPacProxy(TEST_URI);
+
+ assertEquals(LOCAL_HOST, proxy1.getHost());
+ assertEquals(LOCAL_PORT, proxy1.getPort());
+ assertArrayEquals(LOCAL_EXCL_LIST.toLowerCase(Locale.ROOT).split(","),
+ proxy1.getExclusionList());
+ assertEquals(TEST_URI, proxy1.getPacFileUrl());
+
+ final ProxyInfo proxy2 = ProxyInfo.buildPacProxy(TEST_URI, TEST_PORT);
+
+ assertEquals(LOCAL_HOST, proxy2.getHost());
+ assertEquals(TEST_PORT, proxy2.getPort());
+ assertArrayEquals(LOCAL_EXCL_LIST.toLowerCase(Locale.ROOT).split(","),
+ proxy2.getExclusionList());
+ assertEquals(TEST_URI, proxy2.getPacFileUrl());
+ }
+
+ @Test
+ public void testIsValid() {
+ final ProxyInfo proxy1 = ProxyInfo.buildDirectProxy(TEST_HOST, TEST_PORT);
+ assertTrue(proxy1.isValid());
+
+ // Given empty host
+ final ProxyInfo proxy2 = ProxyInfo.buildDirectProxy("", TEST_PORT);
+ assertFalse(proxy2.isValid());
+ // Given invalid host
+ final ProxyInfo proxy3 = ProxyInfo.buildDirectProxy(".invalid.com", TEST_PORT);
+ assertFalse(proxy3.isValid());
+ // Given invalid port.
+ final ProxyInfo proxy4 = ProxyInfo.buildDirectProxy(TEST_HOST, 0);
+ assertFalse(proxy4.isValid());
+ // Given another invalid port
+ final ProxyInfo proxy5 = ProxyInfo.buildDirectProxy(TEST_HOST, 65536);
+ assertFalse(proxy5.isValid());
+ // Given invalid exclusion list
+ final List<String> exclList = new ArrayList<>();
+ exclList.add(".invalid.com");
+ exclList.add("%.test.net");
+ final ProxyInfo proxy6 = ProxyInfo.buildDirectProxy(TEST_HOST, TEST_PORT, exclList);
+ assertFalse(proxy6.isValid());
+ }
+
+ private void checkEmpty(ProxyInfo proxy) {
+ assertNull(proxy.getHost());
+ assertEquals(0, proxy.getPort());
+ assertNull(proxy.getExclusionList());
+ assertEquals(Uri.EMPTY, proxy.getPacFileUrl());
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/TrafficStatsTest.java b/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
index 5bd1e20..37bdd44 100755
--- a/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
+++ b/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
@@ -16,14 +16,13 @@
package android.net.cts;
-import android.content.pm.PackageManager;
import android.net.NetworkStats;
import android.net.TrafficStats;
import android.os.Process;
-import android.os.SystemProperties;
import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
import android.util.Log;
+import android.util.Range;
import java.io.IOException;
import java.io.InputStream;
@@ -36,6 +35,13 @@
public class TrafficStatsTest extends AndroidTestCase {
private static final String LOG_TAG = "TrafficStatsTest";
+ /** Verify the given value is in range [lower, upper] */
+ private void assertInRange(String tag, long value, long lower, long upper) {
+ final Range range = new Range(lower, upper);
+ assertTrue(tag + ": " + value + " is not within range [" + lower + ", " + upper + "]",
+ range.contains(value));
+ }
+
public void testValidMobileStats() {
// We can't assume a mobile network is even present in this test, so
// we simply assert that a valid value is returned.
@@ -53,6 +59,11 @@
assertTrue(TrafficStats.getTotalRxBytes() >= 0);
}
+ public void testValidPacketStats() {
+ assertTrue(TrafficStats.getTxPackets("lo") >= 0);
+ assertTrue(TrafficStats.getRxPackets("lo") >= 0);
+ }
+
public void testThreadStatsTag() throws Exception {
TrafficStats.setThreadStatsTag(0xf00d);
assertTrue("Tag didn't stick", TrafficStats.getThreadStatsTag() == 0xf00d);
@@ -96,6 +107,8 @@
final long uidRxBytesBefore = TrafficStats.getUidRxBytes(Process.myUid());
final long uidTxPacketsBefore = TrafficStats.getUidTxPackets(Process.myUid());
final long uidRxPacketsBefore = TrafficStats.getUidRxPackets(Process.myUid());
+ final long ifaceTxPacketsBefore = TrafficStats.getTxPackets("lo");
+ final long ifaceRxPacketsBefore = TrafficStats.getRxPackets("lo");
// Transfer 1MB of data across an explicitly localhost socket.
final int byteCount = 1024;
@@ -107,12 +120,12 @@
@Override
public void run() {
try {
- Socket socket = new Socket("localhost", server.getLocalPort());
+ final Socket socket = new Socket("localhost", server.getLocalPort());
// Make sure that each write()+flush() turns into a packet:
// disable Nagle.
socket.setTcpNoDelay(true);
- OutputStream out = socket.getOutputStream();
- byte[] buf = new byte[byteCount];
+ final OutputStream out = socket.getOutputStream();
+ final byte[] buf = new byte[byteCount];
TrafficStats.setThreadStatsTag(0x42);
TrafficStats.tagSocket(socket);
for (int i = 0; i < packetCount; i++) {
@@ -135,12 +148,12 @@
int read = 0;
try {
- Socket socket = server.accept();
+ final Socket socket = server.accept();
socket.setTcpNoDelay(true);
TrafficStats.setThreadStatsTag(0x43);
TrafficStats.tagSocket(socket);
- InputStream in = socket.getInputStream();
- byte[] buf = new byte[byteCount];
+ final InputStream in = socket.getInputStream();
+ final byte[] buf = new byte[byteCount];
while (read < byteCount * packetCount) {
int n = in.read(buf);
assertTrue("Unexpected EOF", n > 0);
@@ -156,24 +169,28 @@
Thread.sleep(1000);
} catch (InterruptedException e) {
}
- NetworkStats testStats = TrafficStats.stopDataProfiling(null);
+ final NetworkStats testStats = TrafficStats.stopDataProfiling(null);
- long mobileTxPacketsAfter = TrafficStats.getMobileTxPackets();
- long mobileRxPacketsAfter = TrafficStats.getMobileRxPackets();
- long mobileTxBytesAfter = TrafficStats.getMobileTxBytes();
- long mobileRxBytesAfter = TrafficStats.getMobileRxBytes();
- long totalTxPacketsAfter = TrafficStats.getTotalTxPackets();
- long totalRxPacketsAfter = TrafficStats.getTotalRxPackets();
- long totalTxBytesAfter = TrafficStats.getTotalTxBytes();
- long totalRxBytesAfter = TrafficStats.getTotalRxBytes();
- long uidTxBytesAfter = TrafficStats.getUidTxBytes(Process.myUid());
- long uidRxBytesAfter = TrafficStats.getUidRxBytes(Process.myUid());
- long uidTxPacketsAfter = TrafficStats.getUidTxPackets(Process.myUid());
- long uidRxPacketsAfter = TrafficStats.getUidRxPackets(Process.myUid());
- long uidTxDeltaBytes = uidTxBytesAfter - uidTxBytesBefore;
- long uidTxDeltaPackets = uidTxPacketsAfter - uidTxPacketsBefore;
- long uidRxDeltaBytes = uidRxBytesAfter - uidRxBytesBefore;
- long uidRxDeltaPackets = uidRxPacketsAfter - uidRxPacketsBefore;
+ final long mobileTxPacketsAfter = TrafficStats.getMobileTxPackets();
+ final long mobileRxPacketsAfter = TrafficStats.getMobileRxPackets();
+ final long mobileTxBytesAfter = TrafficStats.getMobileTxBytes();
+ final long mobileRxBytesAfter = TrafficStats.getMobileRxBytes();
+ final long totalTxPacketsAfter = TrafficStats.getTotalTxPackets();
+ final long totalRxPacketsAfter = TrafficStats.getTotalRxPackets();
+ final long totalTxBytesAfter = TrafficStats.getTotalTxBytes();
+ final long totalRxBytesAfter = TrafficStats.getTotalRxBytes();
+ final long uidTxBytesAfter = TrafficStats.getUidTxBytes(Process.myUid());
+ final long uidRxBytesAfter = TrafficStats.getUidRxBytes(Process.myUid());
+ final long uidTxPacketsAfter = TrafficStats.getUidTxPackets(Process.myUid());
+ final long uidRxPacketsAfter = TrafficStats.getUidRxPackets(Process.myUid());
+ final long uidTxDeltaBytes = uidTxBytesAfter - uidTxBytesBefore;
+ final long uidTxDeltaPackets = uidTxPacketsAfter - uidTxPacketsBefore;
+ final long uidRxDeltaBytes = uidRxBytesAfter - uidRxBytesBefore;
+ final long uidRxDeltaPackets = uidRxPacketsAfter - uidRxPacketsBefore;
+ final long ifaceTxPacketsAfter = TrafficStats.getTxPackets("lo");
+ final long ifaceRxPacketsAfter = TrafficStats.getRxPackets("lo");
+ final long ifaceTxDeltaPackets = ifaceTxPacketsAfter - ifaceTxPacketsBefore;
+ final long ifaceRxDeltaPackets = ifaceRxPacketsAfter - ifaceRxPacketsBefore;
// Localhost traffic *does* count against per-UID stats.
/*
@@ -192,50 +209,46 @@
// Some other tests don't cleanup connections correctly.
// They have the same UID, so we discount their lingering traffic
// which happens only on non-localhost, such as TCP FIN retranmission packets
- long deltaTxOtherPackets = (totalTxPacketsAfter - totalTxPacketsBefore) - uidTxDeltaPackets;
- long deltaRxOtherPackets = (totalRxPacketsAfter - totalRxPacketsBefore) - uidRxDeltaPackets;
+ final long deltaTxOtherPackets = (totalTxPacketsAfter - totalTxPacketsBefore)
+ - uidTxDeltaPackets;
+ final long deltaRxOtherPackets = (totalRxPacketsAfter - totalRxPacketsBefore)
+ - uidRxDeltaPackets;
if (deltaTxOtherPackets > 0 || deltaRxOtherPackets > 0) {
- Log.i(LOG_TAG, "lingering traffic data: " + deltaTxOtherPackets + "/" + deltaRxOtherPackets);
+ Log.i(LOG_TAG, "lingering traffic data: " + deltaTxOtherPackets + "/"
+ + deltaRxOtherPackets);
}
- // Check the per uid stats read from data profiling have the stats expected. The data
- // profiling snapshot is generated from readNetworkStatsDetail() method in
- // networkStatsService and in this way we can verify the detail networkStats of a given uid
- // is correct.
- NetworkStats.Entry entry = testStats.getTotal(null, Process.myUid());
- assertTrue("txPackets detail: " + entry.txPackets + " uidTxPackets: " + uidTxDeltaPackets,
- entry.txPackets >= packetCount + minExpectedExtraPackets
- && entry.txPackets <= uidTxDeltaPackets);
- assertTrue("rxPackets detail: " + entry.rxPackets + " uidRxPackets: " + uidRxDeltaPackets,
- entry.rxPackets >= packetCount + minExpectedExtraPackets
- && entry.rxPackets <= uidRxDeltaPackets);
- assertTrue("txBytes detail: " + entry.txBytes + " uidTxDeltaBytes: " + uidTxDeltaBytes,
- entry.txBytes >= tcpPacketToIpBytes(packetCount, byteCount)
- + tcpPacketToIpBytes(minExpectedExtraPackets, 0) && entry.txBytes <= uidTxDeltaBytes);
- assertTrue("rxBytes detail: " + entry.rxBytes + " uidRxDeltaBytes: " + uidRxDeltaBytes,
- entry.rxBytes >= tcpPacketToIpBytes(packetCount, byteCount)
- + tcpPacketToIpBytes(minExpectedExtraPackets, 0) && entry.rxBytes <= uidRxDeltaBytes);
-
- assertTrue("uidtxp: " + uidTxPacketsBefore + " -> " + uidTxPacketsAfter + " delta=" + uidTxDeltaPackets +
- " Wanted: " + uidTxDeltaPackets + ">=" + packetCount + "+" + minExpectedExtraPackets + " && " +
- uidTxDeltaPackets + "<=" + packetCount + "+" + packetCount + "+" + maxExpectedExtraPackets + "+" + deltaTxOtherPackets,
- uidTxDeltaPackets >= packetCount + minExpectedExtraPackets &&
- uidTxDeltaPackets <= packetCount + packetCount + maxExpectedExtraPackets + deltaTxOtherPackets);
- assertTrue("uidrxp: " + uidRxPacketsBefore + " -> " + uidRxPacketsAfter + " delta=" + uidRxDeltaPackets +
- " Wanted: " + uidRxDeltaPackets + ">=" + packetCount + "+" + minExpectedExtraPackets + " && " +
- uidRxDeltaPackets + "<=" + packetCount + "+" + packetCount + "+" + maxExpectedExtraPackets,
- uidRxDeltaPackets >= packetCount + minExpectedExtraPackets &&
- uidRxDeltaPackets <= packetCount + packetCount + maxExpectedExtraPackets + deltaRxOtherPackets);
- assertTrue("uidtxb: " + uidTxBytesBefore + " -> " + uidTxBytesAfter + " delta=" + uidTxDeltaBytes +
- " Wanted: " + uidTxDeltaBytes + ">=" + tcpPacketToIpBytes(packetCount, byteCount) + "+" + tcpPacketToIpBytes(minExpectedExtraPackets, 0) + " && " +
- uidTxDeltaBytes + "<=" + tcpPacketToIpBytes(packetCount, byteCount) + "+" + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets, 0),
- uidTxDeltaBytes >= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(minExpectedExtraPackets, 0) &&
- uidTxDeltaBytes <= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets + deltaTxOtherPackets, 0));
- assertTrue("uidrxb: " + uidRxBytesBefore + " -> " + uidRxBytesAfter + " delta=" + uidRxDeltaBytes +
- " Wanted: " + uidRxDeltaBytes + ">=" + tcpPacketToIpBytes(packetCount, byteCount) + "+" + tcpPacketToIpBytes(minExpectedExtraPackets, 0) + " && " +
- uidRxDeltaBytes + "<=" + tcpPacketToIpBytes(packetCount, byteCount) + "+" + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets, 0),
- uidRxDeltaBytes >= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(minExpectedExtraPackets, 0) &&
- uidRxDeltaBytes <= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets + deltaRxOtherPackets, 0));
+ // Check that the per-uid stats obtained from data profiling contain the expected values.
+ // The data profiling snapshot is generated from the readNetworkStatsDetail() method in
+ // networkStatsService, so it's possible to verify that the detailed stats for a given
+ // uid are correct.
+ final NetworkStats.Entry entry = testStats.getTotal(null, Process.myUid());
+ final long pktBytes = tcpPacketToIpBytes(packetCount, byteCount);
+ final long pktWithNoDataBytes = tcpPacketToIpBytes(packetCount, 0);
+ final long minExpExtraPktBytes = tcpPacketToIpBytes(minExpectedExtraPackets, 0);
+ final long maxExpExtraPktBytes = tcpPacketToIpBytes(maxExpectedExtraPackets, 0);
+ final long deltaTxOtherPktBytes = tcpPacketToIpBytes(deltaTxOtherPackets, 0);
+ final long deltaRxOtherPktBytes = tcpPacketToIpBytes(deltaRxOtherPackets, 0);
+ assertInRange("txPackets detail", entry.txPackets, packetCount + minExpectedExtraPackets,
+ uidTxDeltaPackets);
+ assertInRange("rxPackets detail", entry.rxPackets, packetCount + minExpectedExtraPackets,
+ uidRxDeltaPackets);
+ assertInRange("txBytes detail", entry.txBytes, pktBytes + minExpExtraPktBytes,
+ uidTxDeltaBytes);
+ assertInRange("rxBytes detail", entry.rxBytes, pktBytes + minExpExtraPktBytes,
+ uidRxDeltaBytes);
+ assertInRange("uidtxp", uidTxDeltaPackets, packetCount + minExpectedExtraPackets,
+ packetCount + packetCount + maxExpectedExtraPackets + deltaTxOtherPackets);
+ assertInRange("uidrxp", uidRxDeltaPackets, packetCount + minExpectedExtraPackets,
+ packetCount + packetCount + maxExpectedExtraPackets + deltaRxOtherPackets);
+ assertInRange("uidtxb", uidTxDeltaBytes, pktBytes + minExpExtraPktBytes,
+ pktBytes + pktWithNoDataBytes + maxExpExtraPktBytes + deltaTxOtherPktBytes);
+ assertInRange("uidrxb", uidRxDeltaBytes, pktBytes + minExpExtraPktBytes,
+ pktBytes + pktWithNoDataBytes + maxExpExtraPktBytes + deltaRxOtherPktBytes);
+ assertInRange("iftxp", ifaceTxDeltaPackets, packetCount + minExpectedExtraPackets,
+ packetCount + packetCount + maxExpectedExtraPackets + deltaTxOtherPackets);
+ assertInRange("ifrxp", ifaceRxDeltaPackets, packetCount + minExpectedExtraPackets,
+ packetCount + packetCount + maxExpectedExtraPackets + deltaRxOtherPackets);
// Localhost traffic *does* count against total stats.
// Check the total stats increased after test data transfer over localhost has been made.
@@ -247,42 +260,20 @@
totalTxBytesAfter >= totalTxBytesBefore + uidTxDeltaBytes);
assertTrue("trxb: " + totalRxBytesBefore + " -> " + totalRxBytesAfter,
totalRxBytesAfter >= totalRxBytesBefore + uidRxDeltaBytes);
-
- // If the adb TCP port is opened, this test may be run by adb over network.
- // Huge amount of data traffic might go through the network and accounted into total packets
- // stats. The upper bound check would be meaningless.
- // TODO: Consider precisely calculate the traffic accounted due to adb over network and
- // subtract it when checking upper bound instead of skip checking.
- final PackageManager pm = mContext.getPackageManager();
- if (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1
- || SystemProperties.getInt("service.adb.tcp.port", -1) > -1
- || !pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY)) {
- Log.i(LOG_TAG, "adb is running over the network, skip the upper bound check");
- } else {
- // Fudge by 132 packets of 1500 bytes not related to the test.
- assertTrue("ttxp: " + totalTxPacketsBefore + " -> " + totalTxPacketsAfter,
- totalTxPacketsAfter <= totalTxPacketsBefore + uidTxDeltaPackets + 132);
- assertTrue("trxp: " + totalRxPacketsBefore + " -> " + totalRxPacketsAfter,
- totalRxPacketsAfter <= totalRxPacketsBefore + uidRxDeltaPackets + 132);
- assertTrue("ttxb: " + totalTxBytesBefore + " -> " + totalTxBytesAfter,
- totalTxBytesAfter <= totalTxBytesBefore + uidTxDeltaBytes + 132 * 1500);
- assertTrue("trxb: " + totalRxBytesBefore + " -> " + totalRxBytesAfter,
- totalRxBytesAfter <= totalRxBytesBefore + uidRxDeltaBytes + 132 * 1500);
- }
+ assertTrue("iftxp: " + ifaceTxPacketsBefore + " -> " + ifaceTxPacketsAfter,
+ totalTxPacketsAfter >= totalTxPacketsBefore + ifaceTxDeltaPackets);
+ assertTrue("ifrxp: " + ifaceRxPacketsBefore + " -> " + ifaceRxPacketsAfter,
+ totalRxPacketsAfter >= totalRxPacketsBefore + ifaceRxDeltaPackets);
// Localhost traffic should *not* count against mobile stats,
// There might be some other traffic, but nowhere near 1MB.
- assertTrue("mtxp: " + mobileTxPacketsBefore + " -> " + mobileTxPacketsAfter,
- mobileTxPacketsAfter >= mobileTxPacketsBefore &&
- mobileTxPacketsAfter <= mobileTxPacketsBefore + 500);
- assertTrue("mrxp: " + mobileRxPacketsBefore + " -> " + mobileRxPacketsAfter,
- mobileRxPacketsAfter >= mobileRxPacketsBefore &&
- mobileRxPacketsAfter <= mobileRxPacketsBefore + 500);
- assertTrue("mtxb: " + mobileTxBytesBefore + " -> " + mobileTxBytesAfter,
- mobileTxBytesAfter >= mobileTxBytesBefore &&
- mobileTxBytesAfter <= mobileTxBytesBefore + 200000);
- assertTrue("mrxb: " + mobileRxBytesBefore + " -> " + mobileRxBytesAfter,
- mobileRxBytesAfter >= mobileRxBytesBefore &&
- mobileRxBytesAfter <= mobileRxBytesBefore + 200000);
+ assertInRange("mtxp", mobileTxPacketsAfter, mobileTxPacketsBefore,
+ mobileTxPacketsBefore + 500);
+ assertInRange("mrxp", mobileRxPacketsAfter, mobileRxPacketsBefore,
+ mobileRxPacketsBefore + 500);
+ assertInRange("mtxb", mobileTxBytesAfter, mobileTxBytesBefore,
+ mobileTxBytesBefore + 200000);
+ assertInRange("mrxb", mobileRxBytesAfter, mobileRxBytesBefore,
+ mobileRxBytesBefore + 200000);
}
}
diff --git a/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java b/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java
index 2d615bb..5a70928 100644
--- a/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java
+++ b/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java
@@ -16,15 +16,38 @@
package android.net.cts;
-import java.util.List;
-import java.util.Set;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
import android.net.UrlQuerySanitizer;
import android.net.UrlQuerySanitizer.IllegalCharacterValueSanitizer;
import android.net.UrlQuerySanitizer.ParameterValuePair;
import android.net.UrlQuerySanitizer.ValueSanitizer;
-import android.test.AndroidTestCase;
+import android.os.Build;
-public class UrlQuerySanitizerTest extends AndroidTestCase {
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UrlQuerySanitizerTest {
+ @Rule
+ public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
private static final int ALL_OK = IllegalCharacterValueSanitizer.ALL_OK;
// URL for test.
@@ -41,6 +64,7 @@
private static final String AGE = "age";
private static final String HEIGHT = "height";
+ @Test
public void testUrlQuerySanitizer() {
MockUrlQuerySanitizer uqs = new MockUrlQuerySanitizer();
assertFalse(uqs.getAllowUnregisteredParamaters());
@@ -209,6 +233,19 @@
}
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q) // Only fixed in R
+ public void testScriptUrlOk_73822755() {
+ ValueSanitizer sanitizer = new UrlQuerySanitizer.IllegalCharacterValueSanitizer(
+ UrlQuerySanitizer.IllegalCharacterValueSanitizer.SCRIPT_URL_OK);
+ assertEquals("javascript:alert()", sanitizer.sanitize("javascript:alert()"));
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q) // Only fixed in R
+ public void testScriptUrlBlocked_73822755() {
+ ValueSanitizer sanitizer = UrlQuerySanitizer.getUrlAndSpaceLegal();
+ assertEquals("", sanitizer.sanitize("javascript:alert()"));
+ }
+
private static class MockValueSanitizer implements ValueSanitizer{
public String sanitize(String value) {
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index 6214f89..824146f 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -16,6 +16,7 @@
package android.net.cts.util;
+import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -107,6 +108,8 @@
boolean connected = false;
try {
SystemUtil.runShellCommand("svc wifi enable");
+ SystemUtil.runWithShellPermissionIdentity(() -> mWifiManager.reconnect(),
+ NETWORK_SETTINGS);
// Ensure we get both an onAvailable callback and a CONNECTIVITY_ACTION.
wifiNetwork = callback.waitForAvailable();
assertNotNull(wifiNetwork);
diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index 0f98125..85bb0e0 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -25,12 +25,22 @@
],
static_libs: [
+ "TetheringCommonTests",
+ "TetheringIntegrationTestsLib",
"compatibility-device-util-axt",
+ "cts-net-utils",
+ "net-tests-utils",
"ctstestrunner-axt",
"junit",
"junit-params",
],
+ jni_libs: [
+ // For mockito extended
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+
// Change to system current when TetheringManager move to bootclass path.
platform_apis: true,
@@ -41,4 +51,6 @@
"mts",
],
+ // Include both the 32 and 64 bit versions
+ compile_multilib: "both",
}
diff --git a/tests/cts/tethering/OWNERS b/tests/cts/tethering/OWNERS
new file mode 100644
index 0000000..cd6abeb
--- /dev/null
+++ b/tests/cts/tethering/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 31808
+lorenzo@google.com
+satk@google.com
+
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 4d72eae..bbb9403 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -15,21 +15,59 @@
*/
package android.tethering.test;
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
+import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED;
+import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
+import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+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 static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+import android.app.UiAutomation;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.TetheredClient;
import android.net.TetheringManager;
+import android.net.TetheringManager.OnTetheringEntitlementResultListener;
+import android.net.TetheringManager.TetheringEventCallback;
+import android.net.TetheringManager.TetheringInterfaceRegexps;
import android.net.TetheringManager.TetheringRequest;
-import android.os.ConditionVariable;
+import android.net.cts.util.CtsNetUtils;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.telephony.TelephonyManager;
+import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.ArrayTrackRecord;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -37,31 +75,49 @@
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Iterator;
-import java.util.concurrent.Executor;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
@RunWith(AndroidJUnit4.class)
public class TetheringManagerTest {
private Context mContext;
+ private ConnectivityManager mCm;
private TetheringManager mTM;
+ private WifiManager mWm;
+ private PackageManager mPm;
private TetherChangeReceiver mTetherChangeReceiver;
-
- private String[] mTetheredList;
+ private CtsNetUtils mCtsNetUtils;
private static final int DEFAULT_TIMEOUT_MS = 60_000;
+ private void adoptShellPermissionIdentity() {
+ final UiAutomation uiAutomation =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ uiAutomation.adoptShellPermissionIdentity();
+ }
+
+ private void dropShellPermissionIdentity() {
+ final UiAutomation uiAutomation =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ uiAutomation.dropShellPermissionIdentity();
+ }
+
@Before
public void setUp() throws Exception {
- InstrumentationRegistry.getInstrumentation()
- .getUiAutomation()
- .adoptShellPermissionIdentity();
+ adoptShellPermissionIdentity();
mContext = InstrumentationRegistry.getContext();
+ mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
mTM = (TetheringManager) mContext.getSystemService(Context.TETHERING_SERVICE);
+ mWm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ mPm = mContext.getPackageManager();
+ mCtsNetUtils = new CtsNetUtils(mContext);
mTetherChangeReceiver = new TetherChangeReceiver();
final IntentFilter filter = new IntentFilter(
TetheringManager.ACTION_TETHER_STATE_CHANGED);
@@ -71,10 +127,9 @@
@After
public void tearDown() throws Exception {
+ mTM.stopAllTethering();
mContext.unregisterReceiver(mTetherChangeReceiver);
- InstrumentationRegistry.getInstrumentation()
- .getUiAutomation()
- .dropShellPermissionIdentity();
+ dropShellPermissionIdentity();
}
private class TetherChangeReceiver extends BroadcastReceiver {
@@ -103,28 +158,24 @@
public final LinkedBlockingQueue<TetherState> mResult = new LinkedBlockingQueue<>();
- // This method expects either an event where one of the interfaces is active, or events
- // where the interfaces are available followed by one event where one of the interfaces is
- // active. Here is a typical example for wifi tethering:
- // AVAILABLE(wlan0) -> AVAILABLE(wlan1) -> ACTIVATE(wlan1).
- public void expectActiveTethering(String[] ifaceRegexs) {
- TetherState state = null;
+ // Expects that tethering reaches the desired state.
+ // - If active is true, expects that tethering is enabled on at least one interface
+ // matching ifaceRegexs.
+ // - If active is false, expects that tethering is disabled on all the interfaces matching
+ // ifaceRegexs.
+ // Fails if any interface matching ifaceRegexs becomes errored.
+ public void expectTethering(final boolean active, final String[] ifaceRegexs) {
while (true) {
- state = pollAndAssertNoError(DEFAULT_TIMEOUT_MS);
- if (state == null) fail("Do not receive active state change broadcast");
+ final TetherState state = pollAndAssertNoError(DEFAULT_TIMEOUT_MS, ifaceRegexs);
+ assertNotNull("Did not receive expected state change, active: " + active, state);
- if (isIfaceActive(ifaceRegexs, state)) return;
-
- if (!isIfaceAvailable(ifaceRegexs, state)) break;
+ if (isIfaceActive(ifaceRegexs, state) == active) return;
}
-
- fail("Tethering is not actived, available ifaces: " + state.mAvailable.toString()
- + ", active ifaces: " + state.mActive.toString());
}
- private TetherState pollAndAssertNoError(final int timeout) {
+ private TetherState pollAndAssertNoError(final int timeout, final String[] ifaceRegexs) {
final TetherState state = pollTetherState(timeout);
- assertNoErroredIfaces(state);
+ assertNoErroredIfaces(state, ifaceRegexs);
return state;
}
@@ -141,59 +192,72 @@
return isIfaceMatch(ifaceRegexs, state.mActive);
}
- private boolean isIfaceAvailable(final String[] ifaceRegexs, final TetherState state) {
- return isIfaceMatch(ifaceRegexs, state.mAvailable);
- }
-
- // This method requires a broadcast to have been recorded iff the timeout is non-zero.
- public void expectNoActiveTethering(final int timeout) {
- final TetherState state = pollAndAssertNoError(timeout);
-
- if (state == null) {
- if (timeout != 0) {
- fail("Do not receive tethering state change broadcast");
- }
- return;
- }
-
- assertNoActiveIfaces(state);
-
- for (final TetherState ts : mResult) {
- assertNoErroredIfaces(ts);
-
- assertNoActiveIfaces(ts);
- }
- }
-
- private void assertNoErroredIfaces(final TetherState state) {
+ private void assertNoErroredIfaces(final TetherState state, final String[] ifaceRegexs) {
if (state == null || state.mErrored == null) return;
- if (state.mErrored.size() > 0) {
+ if (isIfaceMatch(ifaceRegexs, state.mErrored)) {
fail("Found failed tethering interfaces: " + Arrays.toString(state.mErrored.toArray()));
}
}
-
- private void assertNoActiveIfaces(final TetherState state) {
- if (state.mActive != null && state.mActive.size() > 0) {
- fail("Found active tethering interface: " + Arrays.toString(state.mActive.toArray()));
- }
- }
}
- private class StartTetheringCallback implements TetheringManager.StartTetheringCallback {
+ private static class StartTetheringCallback implements TetheringManager.StartTetheringCallback {
+ private static int TIMEOUT_MS = 30_000;
+ public static class CallbackValue {
+ public final int error;
+
+ private CallbackValue(final int e) {
+ error = e;
+ }
+
+ public static class OnTetheringStarted extends CallbackValue {
+ OnTetheringStarted() { super(TETHER_ERROR_NO_ERROR); }
+ }
+
+ public static class OnTetheringFailed extends CallbackValue {
+ OnTetheringFailed(final int error) { super(error); }
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s(%d)", getClass().getSimpleName(), error);
+ }
+ }
+
+ private final ArrayTrackRecord<CallbackValue>.ReadHead mHistory =
+ new ArrayTrackRecord<CallbackValue>().newReadHead();
+
@Override
public void onTetheringStarted() {
- // Do nothing, TetherChangeReceiver will wait until it receives the broadcast.
+ mHistory.add(new CallbackValue.OnTetheringStarted());
}
@Override
public void onTetheringFailed(final int error) {
- fail("startTethering fail: " + error);
+ mHistory.add(new CallbackValue.OnTetheringFailed(error));
+ }
+
+ public void verifyTetheringStarted() {
+ final CallbackValue cv = mHistory.poll(TIMEOUT_MS, c -> true);
+ assertNotNull("No onTetheringStarted after " + TIMEOUT_MS + " ms", cv);
+ assertTrue("Fail start tethering:" + cv,
+ cv instanceof CallbackValue.OnTetheringStarted);
+ }
+
+ public void expectTetheringFailed(final int expected) throws InterruptedException {
+ final CallbackValue cv = mHistory.poll(TIMEOUT_MS, c -> true);
+ assertNotNull("No onTetheringFailed after " + TIMEOUT_MS + " ms", cv);
+ assertTrue("Expect fail with error code " + expected + ", but received: " + cv,
+ (cv instanceof CallbackValue.OnTetheringFailed) && (cv.error == expected));
}
}
- private static boolean isIfaceMatch(final String[] ifaceRegexs,
- final ArrayList<String> ifaces) {
+ private static boolean isIfaceMatch(final List<String> ifaceRegexs,
+ final List<String> ifaces) {
+ return isIfaceMatch(ifaceRegexs.toArray(new String[0]), ifaces);
+ }
+
+ private static boolean isIfaceMatch(final String[] ifaceRegexs, final List<String> ifaces) {
if (ifaceRegexs == null) fail("ifaceRegexs should not be null");
if (ifaces == null) return false;
@@ -215,14 +279,446 @@
final String[] wifiRegexs = mTM.getTetherableWifiRegexs();
if (wifiRegexs.length == 0) return;
- mTetherChangeReceiver.expectNoActiveTethering(0 /** timeout */);
+ final String[] tetheredIfaces = mTM.getTetheredIfaces();
+ assertTrue(tetheredIfaces.length == 0);
final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
- mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(), c -> c.run(),
- startTetheringCallback);
- mTetherChangeReceiver.expectActiveTethering(wifiRegexs);
+ mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(),
+ c -> c.run() /* executor */, startTetheringCallback);
+ startTetheringCallback.verifyTetheringStarted();
+
+ mTetherChangeReceiver.expectTethering(true /* active */, wifiRegexs);
mTM.stopTethering(TETHERING_WIFI);
- mTetherChangeReceiver.expectNoActiveTethering(DEFAULT_TIMEOUT_MS);
+ mTetherChangeReceiver.expectTethering(false /* active */, wifiRegexs);
+ }
+
+ @Test
+ public void testTetheringRequest() {
+ final TetheringRequest tr = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ assertEquals(TETHERING_WIFI, tr.getTetheringType());
+ assertNull(tr.getLocalIpv4Address());
+ assertNull(tr.getClientStaticIpv4Address());
+ assertFalse(tr.isExemptFromEntitlementCheck());
+ assertTrue(tr.getShouldShowEntitlementUi());
+
+ final LinkAddress localAddr = new LinkAddress("192.168.24.5/24");
+ final LinkAddress clientAddr = new LinkAddress("192.168.24.100/24");
+ final TetheringRequest tr2 = new TetheringRequest.Builder(TETHERING_USB)
+ .setStaticIpv4Addresses(localAddr, clientAddr)
+ .setExemptFromEntitlementCheck(true)
+ .setShouldShowEntitlementUi(false).build();
+
+ assertEquals(localAddr, tr2.getLocalIpv4Address());
+ assertEquals(clientAddr, tr2.getClientStaticIpv4Address());
+ assertEquals(TETHERING_USB, tr2.getTetheringType());
+ assertTrue(tr2.isExemptFromEntitlementCheck());
+ assertFalse(tr2.getShouldShowEntitlementUi());
+ }
+
+ // Must poll the callback before looking at the member.
+ private static class TestTetheringEventCallback implements TetheringEventCallback {
+ private static final int TIMEOUT_MS = 30_000;
+
+ public enum CallbackType {
+ ON_SUPPORTED,
+ ON_UPSTREAM,
+ ON_TETHERABLE_REGEX,
+ ON_TETHERABLE_IFACES,
+ ON_TETHERED_IFACES,
+ ON_ERROR,
+ ON_CLIENTS,
+ ON_OFFLOAD_STATUS,
+ };
+
+ public static class CallbackValue {
+ public final CallbackType callbackType;
+ public final Object callbackParam;
+ public final int callbackParam2;
+
+ private CallbackValue(final CallbackType type, final Object param, final int param2) {
+ this.callbackType = type;
+ this.callbackParam = param;
+ this.callbackParam2 = param2;
+ }
+ }
+
+ private final ArrayTrackRecord<CallbackValue> mHistory =
+ new ArrayTrackRecord<CallbackValue>();
+
+ private final ArrayTrackRecord<CallbackValue>.ReadHead mCurrent =
+ mHistory.newReadHead();
+
+ private TetheringInterfaceRegexps mTetherableRegex;
+ private List<String> mTetherableIfaces;
+ private List<String> mTetheredIfaces;
+
+ @Override
+ public void onTetheringSupported(boolean supported) {
+ mHistory.add(new CallbackValue(CallbackType.ON_SUPPORTED, null, (supported ? 1 : 0)));
+ }
+
+ @Override
+ public void onUpstreamChanged(Network network) {
+ mHistory.add(new CallbackValue(CallbackType.ON_UPSTREAM, network, 0));
+ }
+
+ @Override
+ public void onTetherableInterfaceRegexpsChanged(TetheringInterfaceRegexps reg) {
+ mTetherableRegex = reg;
+ mHistory.add(new CallbackValue(CallbackType.ON_TETHERABLE_REGEX, reg, 0));
+ }
+
+ @Override
+ public void onTetherableInterfacesChanged(List<String> interfaces) {
+ mTetherableIfaces = interfaces;
+ mHistory.add(new CallbackValue(CallbackType.ON_TETHERABLE_IFACES, interfaces, 0));
+ }
+
+ @Override
+ public void onTetheredInterfacesChanged(List<String> interfaces) {
+ mTetheredIfaces = interfaces;
+ mHistory.add(new CallbackValue(CallbackType.ON_TETHERED_IFACES, interfaces, 0));
+ }
+
+ @Override
+ public void onError(String ifName, int error) {
+ mHistory.add(new CallbackValue(CallbackType.ON_ERROR, ifName, error));
+ }
+
+ @Override
+ public void onClientsChanged(Collection<TetheredClient> clients) {
+ mHistory.add(new CallbackValue(CallbackType.ON_CLIENTS, clients, 0));
+ }
+
+ @Override
+ public void onOffloadStatusChanged(int status) {
+ mHistory.add(new CallbackValue(CallbackType.ON_OFFLOAD_STATUS, status, 0));
+ }
+
+ public void expectTetherableInterfacesChanged(@NonNull List<String> regexs) {
+ assertNotNull("No expected tetherable ifaces callback", mCurrent.poll(TIMEOUT_MS,
+ (cv) -> {
+ if (cv.callbackType != CallbackType.ON_TETHERABLE_IFACES) return false;
+ final List<String> interfaces = (List<String>) cv.callbackParam;
+ return isIfaceMatch(regexs, interfaces);
+ }));
+ }
+
+ public void expectTetheredInterfacesChanged(@NonNull List<String> regexs) {
+ assertNotNull("No expected tethered ifaces callback", mCurrent.poll(TIMEOUT_MS,
+ (cv) -> {
+ if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) return false;
+
+ final List<String> interfaces = (List<String>) cv.callbackParam;
+
+ // Null regexs means no active tethering.
+ if (regexs == null) return interfaces.isEmpty();
+
+ return isIfaceMatch(regexs, interfaces);
+ }));
+ }
+
+ public void expectCallbackStarted() {
+ int receivedBitMap = 0;
+ // The each bit represent a type from CallbackType.ON_*.
+ // Expect all of callbacks except for ON_ERROR.
+ final int expectedBitMap = 0xff ^ (1 << CallbackType.ON_ERROR.ordinal());
+ // Receive ON_ERROR on started callback is not matter. It just means tethering is
+ // failed last time, should able to continue the test this time.
+ while ((receivedBitMap & expectedBitMap) != expectedBitMap) {
+ final CallbackValue cv = mCurrent.poll(TIMEOUT_MS, c -> true);
+ if (cv == null) {
+ fail("No expected callbacks, " + "expected bitmap: "
+ + expectedBitMap + ", actual: " + receivedBitMap);
+ }
+
+ receivedBitMap |= (1 << cv.callbackType.ordinal());
+ }
+ }
+
+ public void expectOneOfOffloadStatusChanged(int... offloadStatuses) {
+ assertNotNull("No offload status changed", mCurrent.poll(TIMEOUT_MS, (cv) -> {
+ if (cv.callbackType != CallbackType.ON_OFFLOAD_STATUS) return false;
+
+ final int status = (int) cv.callbackParam;
+ for (int offloadStatus : offloadStatuses) if (offloadStatus == status) return true;
+
+ return false;
+ }));
+ }
+
+ public void expectErrorOrTethered(final String iface) {
+ assertNotNull("No expected callback", mCurrent.poll(TIMEOUT_MS, (cv) -> {
+ if (cv.callbackType == CallbackType.ON_ERROR
+ && iface.equals((String) cv.callbackParam)) {
+ return true;
+ }
+ if (cv.callbackType == CallbackType.ON_TETHERED_IFACES
+ && ((List<String>) cv.callbackParam).contains(iface)) {
+ return true;
+ }
+
+ return false;
+ }));
+ }
+
+ public Network getCurrentValidUpstream() {
+ final CallbackValue result = mCurrent.poll(TIMEOUT_MS, (cv) -> {
+ return (cv.callbackType == CallbackType.ON_UPSTREAM)
+ && cv.callbackParam != null;
+ });
+
+ assertNotNull("No valid upstream", result);
+ return (Network) result.callbackParam;
+ }
+
+ public void assumeTetheringSupported() {
+ final ArrayTrackRecord<CallbackValue>.ReadHead history =
+ mHistory.newReadHead();
+ assertNotNull("No onSupported callback", history.poll(TIMEOUT_MS, (cv) -> {
+ if (cv.callbackType != CallbackType.ON_SUPPORTED) return false;
+
+ assumeTrue(cv.callbackParam2 == 1 /* supported */);
+ return true;
+ }));
+ }
+
+ public TetheringInterfaceRegexps getTetheringInterfaceRegexps() {
+ return mTetherableRegex;
+ }
+
+ public List<String> getTetherableInterfaces() {
+ return mTetherableIfaces;
+ }
+
+ public List<String> getTetheredInterfaces() {
+ return mTetheredIfaces;
+ }
+ }
+
+ private TestTetheringEventCallback registerTetheringEventCallback() {
+ final TestTetheringEventCallback tetherEventCallback =
+ new TestTetheringEventCallback();
+
+ mTM.registerTetheringEventCallback(c -> c.run() /* executor */, tetherEventCallback);
+ tetherEventCallback.expectCallbackStarted();
+
+ return tetherEventCallback;
+ }
+
+ private void unregisterTetheringEventCallback(final TestTetheringEventCallback callback) {
+ mTM.unregisterTetheringEventCallback(callback);
+ }
+
+ private List<String> getWifiTetherableInterfaceRegexps(
+ final TestTetheringEventCallback callback) {
+ return callback.getTetheringInterfaceRegexps().getTetherableWifiRegexs();
+ }
+
+ private boolean isWifiTetheringSupported(final TestTetheringEventCallback callback) {
+ return !getWifiTetherableInterfaceRegexps(callback).isEmpty();
+ }
+
+ private void startWifiTethering(final TestTetheringEventCallback callback)
+ throws InterruptedException {
+ final List<String> wifiRegexs = getWifiTetherableInterfaceRegexps(callback);
+ assertFalse(isIfaceMatch(wifiRegexs, callback.getTetheredInterfaces()));
+
+ final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
+ mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(),
+ c -> c.run() /* executor */, startTetheringCallback);
+ startTetheringCallback.verifyTetheringStarted();
+
+ callback.expectTetheredInterfacesChanged(wifiRegexs);
+
+ callback.expectOneOfOffloadStatusChanged(
+ TETHER_HARDWARE_OFFLOAD_STARTED,
+ TETHER_HARDWARE_OFFLOAD_FAILED);
+ }
+
+ private void stopWifiTethering(final TestTetheringEventCallback callback) {
+ mTM.stopTethering(TETHERING_WIFI);
+ callback.expectTetheredInterfacesChanged(null);
+ callback.expectOneOfOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
+ }
+
+ @Test
+ public void testRegisterTetheringEventCallback() throws Exception {
+ final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback();
+ tetherEventCallback.assumeTetheringSupported();
+
+ if (!isWifiTetheringSupported(tetherEventCallback)) {
+ unregisterTetheringEventCallback(tetherEventCallback);
+ return;
+ }
+
+ startWifiTethering(tetherEventCallback);
+
+ final List<String> tetheredIfaces = tetherEventCallback.getTetheredInterfaces();
+ assertEquals(1, tetheredIfaces.size());
+ final String wifiTetheringIface = tetheredIfaces.get(0);
+
+ stopWifiTethering(tetherEventCallback);
+
+ try {
+ final int ret = mTM.tether(wifiTetheringIface);
+
+ // There is no guarantee that the wifi interface will be available after disabling
+ // the hotspot, so don't fail the test if the call to tether() fails.
+ assumeTrue(ret == TETHER_ERROR_NO_ERROR);
+
+ // If calling #tether successful, there is a callback to tell the result of tethering
+ // setup.
+ tetherEventCallback.expectErrorOrTethered(wifiTetheringIface);
+ } finally {
+ mTM.untether(wifiTetheringIface);
+ unregisterTetheringEventCallback(tetherEventCallback);
+ }
+ }
+
+ @Test
+ public void testGetTetherableInterfaceRegexps() {
+ final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback();
+ tetherEventCallback.assumeTetheringSupported();
+
+ final TetheringInterfaceRegexps tetherableRegexs =
+ tetherEventCallback.getTetheringInterfaceRegexps();
+ final List<String> wifiRegexs = tetherableRegexs.getTetherableWifiRegexs();
+ final List<String> usbRegexs = tetherableRegexs.getTetherableUsbRegexs();
+ final List<String> btRegexs = tetherableRegexs.getTetherableBluetoothRegexs();
+
+ assertEquals(wifiRegexs, Arrays.asList(mTM.getTetherableWifiRegexs()));
+ assertEquals(usbRegexs, Arrays.asList(mTM.getTetherableUsbRegexs()));
+ assertEquals(btRegexs, Arrays.asList(mTM.getTetherableBluetoothRegexs()));
+
+ //Verify that any regex name should only contain in one array.
+ wifiRegexs.forEach(s -> assertFalse(usbRegexs.contains(s)));
+ wifiRegexs.forEach(s -> assertFalse(btRegexs.contains(s)));
+ usbRegexs.forEach(s -> assertFalse(btRegexs.contains(s)));
+
+ unregisterTetheringEventCallback(tetherEventCallback);
+ }
+
+ @Test
+ public void testStopAllTethering() throws Exception {
+ final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback();
+ tetherEventCallback.assumeTetheringSupported();
+
+ try {
+ if (!isWifiTetheringSupported(tetherEventCallback)) return;
+
+ // TODO: start ethernet tethering here when TetheringManagerTest is moved to
+ // TetheringIntegrationTest.
+
+ startWifiTethering(tetherEventCallback);
+
+ mTM.stopAllTethering();
+ tetherEventCallback.expectTetheredInterfacesChanged(null);
+ } finally {
+ unregisterTetheringEventCallback(tetherEventCallback);
+ }
+ }
+
+ @Test
+ public void testEnableTetheringPermission() throws Exception {
+ dropShellPermissionIdentity();
+ final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
+ mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(),
+ c -> c.run() /* executor */, startTetheringCallback);
+ startTetheringCallback.expectTetheringFailed(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+ }
+
+ private class EntitlementResultListener implements OnTetheringEntitlementResultListener {
+ private final CompletableFuture<Integer> future = new CompletableFuture<>();
+
+ @Override
+ public void onTetheringEntitlementResult(int result) {
+ future.complete(result);
+ }
+
+ public int get(long timeout, TimeUnit unit) throws Exception {
+ return future.get(timeout, unit);
+ }
+
+ }
+
+ private void assertEntitlementResult(final Consumer<EntitlementResultListener> functor,
+ final int expect) throws Exception {
+ final EntitlementResultListener listener = new EntitlementResultListener();
+ functor.accept(listener);
+
+ assertEquals(expect, listener.get(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ public void testRequestLatestEntitlementResult() throws Exception {
+ // Verify that requestLatestTetheringEntitlementResult() can get entitlement
+ // result(TETHER_ERROR_ENTITLEMENT_UNKNOWN due to invalid downstream type) via listener.
+ assertEntitlementResult(listener -> mTM.requestLatestTetheringEntitlementResult(
+ TETHERING_WIFI_P2P, false, c -> c.run(), listener),
+ TETHER_ERROR_ENTITLEMENT_UNKNOWN);
+
+ // Verify that requestLatestTetheringEntitlementResult() can get entitlement
+ // result(TETHER_ERROR_ENTITLEMENT_UNKNOWN due to invalid downstream type) via receiver.
+ assertEntitlementResult(listener -> mTM.requestLatestTetheringEntitlementResult(
+ TETHERING_WIFI_P2P,
+ new ResultReceiver(null /* handler */) {
+ @Override
+ public void onReceiveResult(int resultCode, Bundle resultData) {
+ listener.onTetheringEntitlementResult(resultCode);
+ }
+ }, false),
+ TETHER_ERROR_ENTITLEMENT_UNKNOWN);
+
+ // Verify that null listener will cause IllegalArgumentException.
+ try {
+ mTM.requestLatestTetheringEntitlementResult(
+ TETHERING_WIFI, false, c -> c.run(), null);
+ } catch (IllegalArgumentException expect) { }
+ }
+
+ @Test
+ public void testTetheringUpstream() throws Exception {
+ assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
+ final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback();
+ tetherEventCallback.assumeTetheringSupported();
+ final boolean previousWifiEnabledState = mWm.isWifiEnabled();
+
+ try {
+ if (!isWifiTetheringSupported(tetherEventCallback)) return;
+
+ if (previousWifiEnabledState) {
+ mCtsNetUtils.disconnectFromWifi(null);
+ }
+
+ final Network activeNetwork = mCm.getActiveNetwork();
+ assertNotNull("No active network. Please ensure the device has working mobile data.",
+ activeNetwork);
+ final NetworkCapabilities activeNetCap = mCm.getNetworkCapabilities(activeNetwork);
+
+ // If active nework is ETHERNET, tethering may not use cell network as upstream.
+ assumeFalse(activeNetCap.hasTransport(TRANSPORT_ETHERNET));
+
+ assertTrue(activeNetCap.hasTransport(TRANSPORT_CELLULAR));
+
+ startWifiTethering(tetherEventCallback);
+
+ final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ final boolean dunRequired = telephonyManager.isTetheringApnRequired();
+ final int expectedCap = dunRequired ? NET_CAPABILITY_DUN : NET_CAPABILITY_INTERNET;
+ final Network network = tetherEventCallback.getCurrentValidUpstream();
+ final NetworkCapabilities netCap = mCm.getNetworkCapabilities(network);
+ assertTrue(netCap.hasTransport(TRANSPORT_CELLULAR));
+ assertTrue(netCap.hasCapability(expectedCap));
+
+ stopWifiTethering(tetherEventCallback);
+ } finally {
+ unregisterTetheringEventCallback(tetherEventCallback);
+ if (previousWifiEnabledState) {
+ mCtsNetUtils.connectToWifi();
+ }
+ }
}
}