Move net unit tests to packages/Connectivity
Move the tests together with packages/Connectivity code, so both can be
moved to packages/modules/Connectivity together.
Also reorganize unit tests in a unit/ directory, as other tests
(integration/, common/ etc.) have been added in tests/net since they
were created. This makes the directory structure consistent.
Test: atest FrameworksNetTests
Bug: 187814163
Ignore-AOSP-First: needs per-branch move for merge conflicts
Change-Id: I254ffd1c08ec058d594b4ea55cbae5505f8497cc
diff --git a/tests/unit/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/unit/java/android/net/ConnectivityDiagnosticsManagerTest.java
new file mode 100644
index 0000000..06e9405
--- /dev/null
+++ b/tests/unit/java/android/net/ConnectivityDiagnosticsManagerTest.java
@@ -0,0 +1,384 @@
+/*
+ * 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;
+
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsBinder;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
+
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.content.Context;
+import android.os.PersistableBundle;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+import java.util.concurrent.Executor;
+
+@RunWith(JUnit4.class)
+public class ConnectivityDiagnosticsManagerTest {
+ private static final int NET_ID = 1;
+ private static final int DETECTION_METHOD = 2;
+ private static final long TIMESTAMP = 10L;
+ private static final String INTERFACE_NAME = "interface";
+ private static final String BUNDLE_KEY = "key";
+ private static final String BUNDLE_VALUE = "value";
+
+ private static final Executor INLINE_EXECUTOR = x -> x.run();
+
+ @Mock private IConnectivityManager mService;
+ @Mock private ConnectivityDiagnosticsCallback mCb;
+
+ private Context mContext;
+ private ConnectivityDiagnosticsBinder mBinder;
+ private ConnectivityDiagnosticsManager mManager;
+
+ private String mPackageName;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getContext();
+
+ mService = mock(IConnectivityManager.class);
+ mCb = mock(ConnectivityDiagnosticsCallback.class);
+
+ mBinder = new ConnectivityDiagnosticsBinder(mCb, INLINE_EXECUTOR);
+ mManager = new ConnectivityDiagnosticsManager(mContext, mService);
+
+ mPackageName = mContext.getOpPackageName();
+ }
+
+ @After
+ public void tearDown() {
+ // clear ConnectivityDiagnosticsManager callbacks map
+ ConnectivityDiagnosticsManager.sCallbacks.clear();
+ }
+
+ private ConnectivityReport createSampleConnectivityReport() {
+ final LinkProperties linkProperties = new LinkProperties();
+ linkProperties.setInterfaceName(INTERFACE_NAME);
+
+ final NetworkCapabilities networkCapabilities = new NetworkCapabilities();
+ networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
+
+ final PersistableBundle bundle = new PersistableBundle();
+ bundle.putString(BUNDLE_KEY, BUNDLE_VALUE);
+
+ return new ConnectivityReport(
+ new Network(NET_ID), TIMESTAMP, linkProperties, networkCapabilities, bundle);
+ }
+
+ private ConnectivityReport createDefaultConnectivityReport() {
+ return new ConnectivityReport(
+ new Network(0),
+ 0L,
+ new LinkProperties(),
+ new NetworkCapabilities(),
+ PersistableBundle.EMPTY);
+ }
+
+ @Test
+ public void testPersistableBundleEquals() {
+ assertFalse(
+ ConnectivityDiagnosticsManager.persistableBundleEquals(
+ null, PersistableBundle.EMPTY));
+ assertFalse(
+ ConnectivityDiagnosticsManager.persistableBundleEquals(
+ PersistableBundle.EMPTY, null));
+ assertTrue(
+ ConnectivityDiagnosticsManager.persistableBundleEquals(
+ PersistableBundle.EMPTY, PersistableBundle.EMPTY));
+
+ final PersistableBundle a = new PersistableBundle();
+ a.putString(BUNDLE_KEY, BUNDLE_VALUE);
+
+ final PersistableBundle b = new PersistableBundle();
+ b.putString(BUNDLE_KEY, BUNDLE_VALUE);
+
+ final PersistableBundle c = new PersistableBundle();
+ c.putString(BUNDLE_KEY, null);
+
+ assertFalse(
+ ConnectivityDiagnosticsManager.persistableBundleEquals(PersistableBundle.EMPTY, a));
+ assertFalse(
+ ConnectivityDiagnosticsManager.persistableBundleEquals(a, PersistableBundle.EMPTY));
+
+ assertTrue(ConnectivityDiagnosticsManager.persistableBundleEquals(a, b));
+ assertTrue(ConnectivityDiagnosticsManager.persistableBundleEquals(b, a));
+
+ assertFalse(ConnectivityDiagnosticsManager.persistableBundleEquals(a, c));
+ assertFalse(ConnectivityDiagnosticsManager.persistableBundleEquals(c, a));
+ }
+
+ @Test
+ public void testConnectivityReportEquals() {
+ final ConnectivityReport defaultReport = createDefaultConnectivityReport();
+ final ConnectivityReport sampleReport = createSampleConnectivityReport();
+ assertEquals(sampleReport, createSampleConnectivityReport());
+ assertEquals(defaultReport, createDefaultConnectivityReport());
+
+ final LinkProperties linkProperties = sampleReport.getLinkProperties();
+ final NetworkCapabilities networkCapabilities = sampleReport.getNetworkCapabilities();
+ final PersistableBundle bundle = sampleReport.getAdditionalInfo();
+
+ assertNotEquals(
+ createDefaultConnectivityReport(),
+ new ConnectivityReport(
+ new Network(NET_ID),
+ 0L,
+ new LinkProperties(),
+ new NetworkCapabilities(),
+ PersistableBundle.EMPTY));
+ assertNotEquals(
+ createDefaultConnectivityReport(),
+ new ConnectivityReport(
+ new Network(0),
+ TIMESTAMP,
+ new LinkProperties(),
+ new NetworkCapabilities(),
+ PersistableBundle.EMPTY));
+ assertNotEquals(
+ createDefaultConnectivityReport(),
+ new ConnectivityReport(
+ new Network(0),
+ 0L,
+ linkProperties,
+ new NetworkCapabilities(),
+ PersistableBundle.EMPTY));
+ assertNotEquals(
+ createDefaultConnectivityReport(),
+ new ConnectivityReport(
+ new Network(0),
+ TIMESTAMP,
+ new LinkProperties(),
+ networkCapabilities,
+ PersistableBundle.EMPTY));
+ assertNotEquals(
+ createDefaultConnectivityReport(),
+ new ConnectivityReport(
+ new Network(0),
+ TIMESTAMP,
+ new LinkProperties(),
+ new NetworkCapabilities(),
+ bundle));
+ }
+
+ @Test
+ public void testConnectivityReportParcelUnparcel() {
+ assertParcelSane(createSampleConnectivityReport(), 5);
+ }
+
+ private DataStallReport createSampleDataStallReport() {
+ final LinkProperties linkProperties = new LinkProperties();
+ linkProperties.setInterfaceName(INTERFACE_NAME);
+
+ final PersistableBundle bundle = new PersistableBundle();
+ bundle.putString(BUNDLE_KEY, BUNDLE_VALUE);
+
+ final NetworkCapabilities networkCapabilities = new NetworkCapabilities();
+ networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
+
+ return new DataStallReport(
+ new Network(NET_ID),
+ TIMESTAMP,
+ DETECTION_METHOD,
+ linkProperties,
+ networkCapabilities,
+ bundle);
+ }
+
+ private DataStallReport createDefaultDataStallReport() {
+ return new DataStallReport(
+ new Network(0),
+ 0L,
+ 0,
+ new LinkProperties(),
+ new NetworkCapabilities(),
+ PersistableBundle.EMPTY);
+ }
+
+ @Test
+ public void testDataStallReportEquals() {
+ final DataStallReport defaultReport = createDefaultDataStallReport();
+ final DataStallReport sampleReport = createSampleDataStallReport();
+ assertEquals(sampleReport, createSampleDataStallReport());
+ assertEquals(defaultReport, createDefaultDataStallReport());
+
+ final LinkProperties linkProperties = sampleReport.getLinkProperties();
+ final NetworkCapabilities networkCapabilities = sampleReport.getNetworkCapabilities();
+ final PersistableBundle bundle = sampleReport.getStallDetails();
+
+ assertNotEquals(
+ defaultReport,
+ new DataStallReport(
+ new Network(NET_ID),
+ 0L,
+ 0,
+ new LinkProperties(),
+ new NetworkCapabilities(),
+ PersistableBundle.EMPTY));
+ assertNotEquals(
+ defaultReport,
+ new DataStallReport(
+ new Network(0),
+ TIMESTAMP,
+ 0,
+ new LinkProperties(),
+ new NetworkCapabilities(),
+ PersistableBundle.EMPTY));
+ assertNotEquals(
+ defaultReport,
+ new DataStallReport(
+ new Network(0),
+ 0L,
+ DETECTION_METHOD,
+ new LinkProperties(),
+ new NetworkCapabilities(),
+ PersistableBundle.EMPTY));
+ assertNotEquals(
+ defaultReport,
+ new DataStallReport(
+ new Network(0),
+ 0L,
+ 0,
+ linkProperties,
+ new NetworkCapabilities(),
+ PersistableBundle.EMPTY));
+ assertNotEquals(
+ defaultReport,
+ new DataStallReport(
+ new Network(0),
+ 0L,
+ 0,
+ new LinkProperties(),
+ networkCapabilities,
+ PersistableBundle.EMPTY));
+ assertNotEquals(
+ defaultReport,
+ new DataStallReport(
+ new Network(0),
+ 0L,
+ 0,
+ new LinkProperties(),
+ new NetworkCapabilities(),
+ bundle));
+ }
+
+ @Test
+ public void testDataStallReportParcelUnparcel() {
+ assertParcelSane(createSampleDataStallReport(), 6);
+ }
+
+ @Test
+ public void testConnectivityDiagnosticsCallbackOnConnectivityReportAvailable() {
+ mBinder.onConnectivityReportAvailable(createSampleConnectivityReport());
+
+ // The callback will be invoked synchronously by inline executor. Immediately check the
+ // latch without waiting.
+ verify(mCb).onConnectivityReportAvailable(eq(createSampleConnectivityReport()));
+ }
+
+ @Test
+ public void testConnectivityDiagnosticsCallbackOnDataStallSuspected() {
+ mBinder.onDataStallSuspected(createSampleDataStallReport());
+
+ // The callback will be invoked synchronously by inline executor. Immediately check the
+ // latch without waiting.
+ verify(mCb).onDataStallSuspected(eq(createSampleDataStallReport()));
+ }
+
+ @Test
+ public void testConnectivityDiagnosticsCallbackOnNetworkConnectivityReported() {
+ final Network n = new Network(NET_ID);
+ final boolean connectivity = true;
+
+ mBinder.onNetworkConnectivityReported(n, connectivity);
+
+ // The callback will be invoked synchronously by inline executor. Immediately check the
+ // latch without waiting.
+ verify(mCb).onNetworkConnectivityReported(eq(n), eq(connectivity));
+ }
+
+ @Test
+ public void testRegisterConnectivityDiagnosticsCallback() throws Exception {
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+
+ mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb);
+
+ verify(mService).registerConnectivityDiagnosticsCallback(
+ any(ConnectivityDiagnosticsBinder.class), eq(request), eq(mPackageName));
+ assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb));
+ }
+
+ @Test
+ public void testRegisterDuplicateConnectivityDiagnosticsCallback() throws Exception {
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+
+ mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb);
+
+ try {
+ mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb);
+ fail("Duplicate callback registration should fail");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testUnregisterConnectivityDiagnosticsCallback() throws Exception {
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb);
+
+ mManager.unregisterConnectivityDiagnosticsCallback(mCb);
+
+ verify(mService).unregisterConnectivityDiagnosticsCallback(
+ any(ConnectivityDiagnosticsBinder.class));
+ assertFalse(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb));
+
+ // verify that re-registering is successful
+ mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb);
+ verify(mService, times(2)).registerConnectivityDiagnosticsCallback(
+ any(ConnectivityDiagnosticsBinder.class), eq(request), eq(mPackageName));
+ assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb));
+ }
+
+ @Test
+ public void testUnregisterUnknownConnectivityDiagnosticsCallback() throws Exception {
+ mManager.unregisterConnectivityDiagnosticsCallback(mCb);
+
+ verifyNoMoreInteractions(mService);
+ }
+}
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
new file mode 100644
index 0000000..591e0cc
--- /dev/null
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
+import static android.net.NetworkRequest.Type.REQUEST;
+import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
+import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Process;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConnectivityManagerTest {
+
+ @Mock Context mCtx;
+ @Mock IConnectivityManager mService;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ static NetworkCapabilities verifyNetworkCapabilities(
+ int legacyType, int transportType, int... capabilities) {
+ final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
+ assertNotNull(nc);
+ assertTrue(nc.hasTransport(transportType));
+ for (int capability : capabilities) {
+ assertTrue(nc.hasCapability(capability));
+ }
+
+ return nc;
+ }
+
+ static void verifyUnrestrictedNetworkCapabilities(int legacyType, int transportType) {
+ verifyNetworkCapabilities(
+ legacyType,
+ transportType,
+ NET_CAPABILITY_INTERNET,
+ NET_CAPABILITY_NOT_RESTRICTED,
+ NET_CAPABILITY_NOT_VPN,
+ NET_CAPABILITY_TRUSTED);
+ }
+
+ static void verifyRestrictedMobileNetworkCapabilities(int legacyType, int capability) {
+ final NetworkCapabilities nc = verifyNetworkCapabilities(
+ legacyType,
+ TRANSPORT_CELLULAR,
+ capability,
+ NET_CAPABILITY_NOT_VPN,
+ NET_CAPABILITY_TRUSTED);
+
+ assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET));
+ assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ }
+
+ @Test
+ public void testNetworkCapabilitiesForTypeMobile() {
+ verifyUnrestrictedNetworkCapabilities(
+ ConnectivityManager.TYPE_MOBILE, TRANSPORT_CELLULAR);
+ }
+
+ @Test
+ public void testNetworkCapabilitiesForTypeMobileCbs() {
+ verifyRestrictedMobileNetworkCapabilities(
+ ConnectivityManager.TYPE_MOBILE_CBS, NET_CAPABILITY_CBS);
+ }
+
+ @Test
+ public void testNetworkCapabilitiesForTypeMobileDun() {
+ verifyRestrictedMobileNetworkCapabilities(
+ ConnectivityManager.TYPE_MOBILE_DUN, NET_CAPABILITY_DUN);
+ }
+
+ @Test
+ public void testNetworkCapabilitiesForTypeMobileFota() {
+ verifyRestrictedMobileNetworkCapabilities(
+ ConnectivityManager.TYPE_MOBILE_FOTA, NET_CAPABILITY_FOTA);
+ }
+
+ @Test
+ public void testNetworkCapabilitiesForTypeMobileHipri() {
+ verifyUnrestrictedNetworkCapabilities(
+ ConnectivityManager.TYPE_MOBILE_HIPRI, TRANSPORT_CELLULAR);
+ }
+
+ @Test
+ public void testNetworkCapabilitiesForTypeMobileIms() {
+ verifyRestrictedMobileNetworkCapabilities(
+ ConnectivityManager.TYPE_MOBILE_IMS, NET_CAPABILITY_IMS);
+ }
+
+ @Test
+ public void testNetworkCapabilitiesForTypeMobileMms() {
+ final NetworkCapabilities nc = verifyNetworkCapabilities(
+ ConnectivityManager.TYPE_MOBILE_MMS,
+ TRANSPORT_CELLULAR,
+ NET_CAPABILITY_MMS,
+ NET_CAPABILITY_NOT_VPN,
+ NET_CAPABILITY_TRUSTED);
+
+ assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET));
+ }
+
+ @Test
+ public void testNetworkCapabilitiesForTypeMobileSupl() {
+ final NetworkCapabilities nc = verifyNetworkCapabilities(
+ ConnectivityManager.TYPE_MOBILE_SUPL,
+ TRANSPORT_CELLULAR,
+ NET_CAPABILITY_SUPL,
+ NET_CAPABILITY_NOT_VPN,
+ NET_CAPABILITY_TRUSTED);
+
+ assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET));
+ }
+
+ @Test
+ public void testNetworkCapabilitiesForTypeWifi() {
+ verifyUnrestrictedNetworkCapabilities(
+ ConnectivityManager.TYPE_WIFI, TRANSPORT_WIFI);
+ }
+
+ @Test
+ public void testNetworkCapabilitiesForTypeWifiP2p() {
+ final NetworkCapabilities nc = verifyNetworkCapabilities(
+ ConnectivityManager.TYPE_WIFI_P2P,
+ TRANSPORT_WIFI,
+ NET_CAPABILITY_NOT_RESTRICTED, NET_CAPABILITY_NOT_VPN,
+ NET_CAPABILITY_TRUSTED, NET_CAPABILITY_WIFI_P2P);
+
+ assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET));
+ }
+
+ @Test
+ public void testNetworkCapabilitiesForTypeBluetooth() {
+ verifyUnrestrictedNetworkCapabilities(
+ ConnectivityManager.TYPE_BLUETOOTH, TRANSPORT_BLUETOOTH);
+ }
+
+ @Test
+ public void testNetworkCapabilitiesForTypeEthernet() {
+ verifyUnrestrictedNetworkCapabilities(
+ ConnectivityManager.TYPE_ETHERNET, TRANSPORT_ETHERNET);
+ }
+
+ @Test
+ public void testCallbackRelease() throws Exception {
+ ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ NetworkRequest request = makeRequest(1);
+ NetworkCallback callback = mock(ConnectivityManager.NetworkCallback.class);
+ Handler handler = new Handler(Looper.getMainLooper());
+ ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
+
+ // register callback
+ when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
+ anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(request);
+ manager.requestNetwork(request, callback, handler);
+
+ // callback triggers
+ captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_AVAILABLE));
+ verify(callback, timeout(500).times(1)).onAvailable(any(Network.class),
+ any(NetworkCapabilities.class), any(LinkProperties.class), anyBoolean());
+
+ // unregister callback
+ manager.unregisterNetworkCallback(callback);
+ verify(mService, times(1)).releaseNetworkRequest(request);
+
+ // callback does not trigger anymore.
+ captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_LOSING));
+ verify(callback, timeout(500).times(0)).onLosing(any(), anyInt());
+ }
+
+ @Test
+ public void testCallbackRecycling() throws Exception {
+ ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ NetworkRequest req1 = makeRequest(1);
+ NetworkRequest req2 = makeRequest(2);
+ NetworkCallback callback = mock(ConnectivityManager.NetworkCallback.class);
+ Handler handler = new Handler(Looper.getMainLooper());
+ ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
+
+ // register callback
+ when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
+ anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req1);
+ manager.requestNetwork(req1, callback, handler);
+
+ // callback triggers
+ captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_AVAILABLE));
+ verify(callback, timeout(100).times(1)).onAvailable(any(Network.class),
+ any(NetworkCapabilities.class), any(LinkProperties.class), anyBoolean());
+
+ // unregister callback
+ manager.unregisterNetworkCallback(callback);
+ verify(mService, times(1)).releaseNetworkRequest(req1);
+
+ // callback does not trigger anymore.
+ captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_LOSING));
+ verify(callback, timeout(100).times(0)).onLosing(any(), anyInt());
+
+ // callback can be registered again
+ when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
+ anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req2);
+ manager.requestNetwork(req2, callback, handler);
+
+ // callback triggers
+ captor.getValue().send(makeMessage(req2, ConnectivityManager.CALLBACK_LOST));
+ verify(callback, timeout(100).times(1)).onLost(any());
+
+ // unregister callback
+ manager.unregisterNetworkCallback(callback);
+ verify(mService, times(1)).releaseNetworkRequest(req2);
+ }
+
+ // TODO: turn on this test when request callback 1:1 mapping is enforced
+ //@Test
+ private void noDoubleCallbackRegistration() throws Exception {
+ ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ NetworkRequest request = makeRequest(1);
+ NetworkCallback callback = new ConnectivityManager.NetworkCallback();
+ ApplicationInfo info = new ApplicationInfo();
+ // TODO: update version when starting to enforce 1:1 mapping
+ info.targetSdkVersion = VERSION_CODES.N_MR1 + 1;
+
+ when(mCtx.getApplicationInfo()).thenReturn(info);
+ when(mService.requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(),
+ anyInt(), any(), nullable(String.class))).thenReturn(request);
+
+ Handler handler = new Handler(Looper.getMainLooper());
+ manager.requestNetwork(request, callback, handler);
+
+ // callback is already registered, reregistration should fail.
+ Class<IllegalArgumentException> wantException = IllegalArgumentException.class;
+ expectThrowable(() -> manager.requestNetwork(request, callback), wantException);
+
+ manager.unregisterNetworkCallback(callback);
+ verify(mService, times(1)).releaseNetworkRequest(request);
+
+ // unregistering the callback should make it registrable again.
+ manager.requestNetwork(request, callback);
+ }
+
+ @Test
+ public void testArgumentValidation() throws Exception {
+ ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+
+ NetworkRequest request = mock(NetworkRequest.class);
+ NetworkCallback callback = mock(NetworkCallback.class);
+ Handler handler = mock(Handler.class);
+ NetworkCallback nullCallback = null;
+ PendingIntent nullIntent = null;
+
+ mustFail(() -> { manager.requestNetwork(null, callback); });
+ mustFail(() -> { manager.requestNetwork(request, nullCallback); });
+ mustFail(() -> { manager.requestNetwork(request, callback, null); });
+ mustFail(() -> { manager.requestNetwork(request, callback, -1); });
+ mustFail(() -> { manager.requestNetwork(request, nullIntent); });
+
+ mustFail(() -> { manager.registerNetworkCallback(null, callback, handler); });
+ mustFail(() -> { manager.registerNetworkCallback(request, null, handler); });
+ mustFail(() -> { manager.registerNetworkCallback(request, callback, null); });
+ mustFail(() -> { manager.registerNetworkCallback(request, nullIntent); });
+
+ mustFail(() -> { manager.registerDefaultNetworkCallback(null, handler); });
+ mustFail(() -> { manager.registerDefaultNetworkCallback(callback, null); });
+
+ mustFail(() -> { manager.registerSystemDefaultNetworkCallback(null, handler); });
+ mustFail(() -> { manager.registerSystemDefaultNetworkCallback(callback, null); });
+
+ mustFail(() -> { manager.unregisterNetworkCallback(nullCallback); });
+ mustFail(() -> { manager.unregisterNetworkCallback(nullIntent); });
+ mustFail(() -> { manager.releaseNetworkRequest(nullIntent); });
+ }
+
+ static void mustFail(Runnable fn) {
+ try {
+ fn.run();
+ fail();
+ } catch (Exception expected) {
+ }
+ }
+
+ @Test
+ public void testRequestType() throws Exception {
+ final String testPkgName = "MyPackage";
+ final String testAttributionTag = "MyTag";
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ when(mCtx.getOpPackageName()).thenReturn(testPkgName);
+ when(mCtx.getAttributionTag()).thenReturn(testAttributionTag);
+ final NetworkRequest request = makeRequest(1);
+ final NetworkCallback callback = new ConnectivityManager.NetworkCallback();
+
+ manager.requestNetwork(request, callback);
+ verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
+ eq(REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
+ eq(testPkgName), eq(testAttributionTag));
+ reset(mService);
+
+ // Verify that register network callback does not calls requestNetwork at all.
+ manager.registerNetworkCallback(request, callback);
+ verify(mService, never()).requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(),
+ anyInt(), anyInt(), any(), any());
+ verify(mService).listenForNetwork(eq(request.networkCapabilities), any(), any(), anyInt(),
+ eq(testPkgName), eq(testAttributionTag));
+ reset(mService);
+
+ Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+
+ manager.registerDefaultNetworkCallback(callback);
+ verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null),
+ eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
+ eq(testPkgName), eq(testAttributionTag));
+ reset(mService);
+
+ manager.registerDefaultNetworkCallbackForUid(42, callback, handler);
+ verify(mService).requestNetwork(eq(42), eq(null),
+ eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
+ eq(testPkgName), eq(testAttributionTag));
+
+ manager.requestBackgroundNetwork(request, callback, handler);
+ verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
+ eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
+ eq(testPkgName), eq(testAttributionTag));
+ reset(mService);
+
+ manager.registerSystemDefaultNetworkCallback(callback, handler);
+ verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null),
+ eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
+ eq(testPkgName), eq(testAttributionTag));
+ reset(mService);
+ }
+
+ static Message makeMessage(NetworkRequest req, int messageType) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(NetworkRequest.class.getSimpleName(), req);
+ // Pass default objects as we don't care which get passed here
+ bundle.putParcelable(Network.class.getSimpleName(), new Network(1));
+ bundle.putParcelable(NetworkCapabilities.class.getSimpleName(), new NetworkCapabilities());
+ bundle.putParcelable(LinkProperties.class.getSimpleName(), new LinkProperties());
+ Message msg = Message.obtain();
+ msg.what = messageType;
+ msg.setData(bundle);
+ return msg;
+ }
+
+ static NetworkRequest makeRequest(int requestId) {
+ NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
+ return new NetworkRequest(request.networkCapabilities, ConnectivityManager.TYPE_NONE,
+ requestId, NetworkRequest.Type.NONE);
+ }
+
+ static void expectThrowable(Runnable block, Class<? extends Throwable> throwableType) {
+ try {
+ block.run();
+ } catch (Throwable t) {
+ if (t.getClass().equals(throwableType)) {
+ return;
+ }
+ fail("expected exception of type " + throwableType + ", but was " + t.getClass());
+ }
+ fail("expected exception of type " + throwableType);
+ }
+}
diff --git a/tests/unit/java/android/net/Ikev2VpnProfileTest.java b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
new file mode 100644
index 0000000..1abd39a
--- /dev/null
+++ b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.test.mock.MockContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.net.VpnProfile;
+import com.android.net.module.util.ProxyUtils;
+import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.security.auth.x500.X500Principal;
+
+/** Unit tests for {@link Ikev2VpnProfile.Builder}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class Ikev2VpnProfileTest {
+ private static final String SERVER_ADDR_STRING = "1.2.3.4";
+ private static final String IDENTITY_STRING = "Identity";
+ private static final String USERNAME_STRING = "username";
+ private static final String PASSWORD_STRING = "pa55w0rd";
+ private static final String EXCL_LIST = "exclList";
+ private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
+ private static final int TEST_MTU = 1300;
+
+ private final MockContext mMockContext =
+ new MockContext() {
+ @Override
+ public String getOpPackageName() {
+ return "fooPackage";
+ }
+ };
+ private final ProxyInfo mProxy = ProxyInfo.buildDirectProxy(
+ SERVER_ADDR_STRING, -1, ProxyUtils.exclusionStringAsList(EXCL_LIST));
+
+ private X509Certificate mUserCert;
+ private X509Certificate mServerRootCa;
+ private PrivateKey mPrivateKey;
+
+ @Before
+ public void setUp() throws Exception {
+ mServerRootCa = generateRandomCertAndKeyPair().cert;
+
+ final CertificateAndKey userCertKey = generateRandomCertAndKeyPair();
+ mUserCert = userCertKey.cert;
+ mPrivateKey = userCertKey.key;
+ }
+
+ private Ikev2VpnProfile.Builder getBuilderWithDefaultOptions() {
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING);
+
+ builder.setBypassable(true);
+ builder.setProxy(mProxy);
+ builder.setMaxMtu(TEST_MTU);
+ builder.setMetered(true);
+
+ return builder;
+ }
+
+ @Test
+ public void testBuildValidProfileWithOptions() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ // Check non-auth parameters correctly stored
+ assertEquals(SERVER_ADDR_STRING, profile.getServerAddr());
+ assertEquals(IDENTITY_STRING, profile.getUserIdentity());
+ assertEquals(mProxy, profile.getProxyInfo());
+ assertTrue(profile.isBypassable());
+ assertTrue(profile.isMetered());
+ assertEquals(TEST_MTU, profile.getMaxMtu());
+ assertEquals(Ikev2VpnProfile.DEFAULT_ALGORITHMS, profile.getAllowedAlgorithms());
+ }
+
+ @Test
+ public void testBuildUsernamePasswordProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ assertEquals(USERNAME_STRING, profile.getUsername());
+ assertEquals(PASSWORD_STRING, profile.getPassword());
+ assertEquals(mServerRootCa, profile.getServerRootCaCert());
+
+ assertNull(profile.getPresharedKey());
+ assertNull(profile.getRsaPrivateKey());
+ assertNull(profile.getUserCert());
+ }
+
+ @Test
+ public void testBuildDigitalSignatureProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ assertEquals(profile.getUserCert(), mUserCert);
+ assertEquals(mPrivateKey, profile.getRsaPrivateKey());
+ assertEquals(profile.getServerRootCaCert(), mServerRootCa);
+
+ assertNull(profile.getPresharedKey());
+ assertNull(profile.getUsername());
+ assertNull(profile.getPassword());
+ }
+
+ @Test
+ public void testBuildPresharedKeyProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ assertArrayEquals(PSK_BYTES, profile.getPresharedKey());
+
+ assertNull(profile.getServerRootCaCert());
+ assertNull(profile.getUsername());
+ assertNull(profile.getPassword());
+ assertNull(profile.getRsaPrivateKey());
+ assertNull(profile.getUserCert());
+ }
+
+ @Test
+ public void testBuildWithAllowedAlgorithmsAead() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthPsk(PSK_BYTES);
+
+ List<String> allowedAlgorithms = Arrays.asList(IpSecAlgorithm.AUTH_CRYPT_AES_GCM);
+ builder.setAllowedAlgorithms(allowedAlgorithms);
+
+ final Ikev2VpnProfile profile = builder.build();
+ assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
+ }
+
+ @Test
+ public void testBuildWithAllowedAlgorithmsNormal() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthPsk(PSK_BYTES);
+
+ List<String> allowedAlgorithms =
+ Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA512, IpSecAlgorithm.CRYPT_AES_CBC);
+ builder.setAllowedAlgorithms(allowedAlgorithms);
+
+ final Ikev2VpnProfile profile = builder.build();
+ assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
+ }
+
+ @Test
+ public void testSetAllowedAlgorithmsEmptyList() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setAllowedAlgorithms(new ArrayList<>());
+ fail("Expected exception due to no valid algorithm set");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSetAllowedAlgorithmsInvalidList() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ List<String> allowedAlgorithms = new ArrayList<>();
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA256));
+ fail("Expected exception due to missing encryption");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.CRYPT_AES_CBC));
+ fail("Expected exception due to missing authentication");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSetAllowedAlgorithmsInsecureAlgorithm() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ List<String> allowedAlgorithms = new ArrayList<>();
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_MD5));
+ fail("Expected exception due to insecure algorithm");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA1));
+ fail("Expected exception due to insecure algorithm");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildNoAuthMethodSet() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.build();
+ fail("Expected exception due to lack of auth method");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildInvalidMtu() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setMaxMtu(500);
+ fail("Expected exception due to too-small MTU");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ private void verifyVpnProfileCommon(VpnProfile profile) {
+ assertEquals(SERVER_ADDR_STRING, profile.server);
+ assertEquals(IDENTITY_STRING, profile.ipsecIdentifier);
+ assertEquals(mProxy, profile.proxy);
+ assertTrue(profile.isBypassable);
+ assertTrue(profile.isMetered);
+ assertEquals(TEST_MTU, profile.maxMtu);
+ }
+
+ @Test
+ public void testPskConvertToVpnProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final VpnProfile profile = builder.build().toVpnProfile();
+
+ verifyVpnProfileCommon(profile);
+ assertEquals(Ikev2VpnProfile.encodeForIpsecSecret(PSK_BYTES), profile.ipsecSecret);
+
+ // Check nothing else is set
+ assertEquals("", profile.username);
+ assertEquals("", profile.password);
+ assertEquals("", profile.ipsecUserCert);
+ assertEquals("", profile.ipsecCaCert);
+ }
+
+ @Test
+ public void testUsernamePasswordConvertToVpnProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+
+ verifyVpnProfileCommon(profile);
+ assertEquals(USERNAME_STRING, profile.username);
+ assertEquals(PASSWORD_STRING, profile.password);
+ assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
+
+ // Check nothing else is set
+ assertEquals("", profile.ipsecUserCert);
+ assertEquals("", profile.ipsecSecret);
+ }
+
+ @Test
+ public void testRsaConvertToVpnProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+
+ final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE
+ + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded());
+ verifyVpnProfileCommon(profile);
+ assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert);
+ assertEquals(
+ expectedSecret,
+ profile.ipsecSecret);
+ assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
+
+ // Check nothing else is set
+ assertEquals("", profile.username);
+ assertEquals("", profile.password);
+ }
+
+ @Test
+ public void testPskFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final VpnProfile profile = builder.build().toVpnProfile();
+ profile.username = USERNAME_STRING;
+ profile.password = PASSWORD_STRING;
+ profile.ipsecCaCert = Ikev2VpnProfile.certificateToPemString(mServerRootCa);
+ profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
+
+ final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+ assertNull(result.getUsername());
+ assertNull(result.getPassword());
+ assertNull(result.getUserCert());
+ assertNull(result.getRsaPrivateKey());
+ assertNull(result.getServerRootCaCert());
+ }
+
+ @Test
+ public void testUsernamePasswordFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+ profile.ipsecSecret = new String(PSK_BYTES);
+ profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
+
+ final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+ assertNull(result.getPresharedKey());
+ assertNull(result.getUserCert());
+ assertNull(result.getRsaPrivateKey());
+ }
+
+ @Test
+ public void testRsaFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+ profile.username = USERNAME_STRING;
+ profile.password = PASSWORD_STRING;
+
+ final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+ assertNull(result.getUsername());
+ assertNull(result.getPassword());
+ assertNull(result.getPresharedKey());
+ }
+
+ @Test
+ public void testPskConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testUsernamePasswordConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testRsaConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ private static class CertificateAndKey {
+ public final X509Certificate cert;
+ public final PrivateKey key;
+
+ CertificateAndKey(X509Certificate cert, PrivateKey key) {
+ this.cert = cert;
+ this.key = key;
+ }
+ }
+
+ private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception {
+ final Date validityBeginDate =
+ new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L));
+ final Date validityEndDate =
+ new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L));
+
+ // Generate a keypair
+ final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(512);
+ final KeyPair keyPair = keyPairGenerator.generateKeyPair();
+
+ final X500Principal dnName = new X500Principal("CN=test.android.com");
+ final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
+ certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
+ certGen.setSubjectDN(dnName);
+ certGen.setIssuerDN(dnName);
+ certGen.setNotBefore(validityBeginDate);
+ certGen.setNotAfter(validityEndDate);
+ certGen.setPublicKey(keyPair.getPublic());
+ certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
+
+ final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL");
+ return new CertificateAndKey(cert, keyPair.getPrivate());
+ }
+}
diff --git a/tests/unit/java/android/net/IpMemoryStoreTest.java b/tests/unit/java/android/net/IpMemoryStoreTest.java
new file mode 100644
index 0000000..0b13800
--- /dev/null
+++ b/tests/unit/java/android/net/IpMemoryStoreTest.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.Status;
+import android.net.networkstack.ModuleNetworkStackClient;
+import android.os.RemoteException;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpMemoryStoreTest {
+ private static final String TAG = IpMemoryStoreTest.class.getSimpleName();
+ private static final String TEST_CLIENT_ID = "testClientId";
+ private static final String TEST_DATA_NAME = "testData";
+ private static final String TEST_OTHER_DATA_NAME = TEST_DATA_NAME + "Other";
+ private static final byte[] TEST_BLOB_DATA = new byte[] { -3, 6, 8, -9, 12,
+ -128, 0, 89, 112, 91, -34 };
+ private static final NetworkAttributes TEST_NETWORK_ATTRIBUTES = buildTestNetworkAttributes(
+ "hint", 219);
+
+ @Mock
+ Context mMockContext;
+ @Mock
+ ModuleNetworkStackClient mModuleNetworkStackClient;
+ @Mock
+ IIpMemoryStore mMockService;
+ @Mock
+ IOnStatusListener mIOnStatusListener;
+ IpMemoryStore mStore;
+
+ @Captor
+ ArgumentCaptor<IIpMemoryStoreCallbacks> mCbCaptor;
+ @Captor
+ ArgumentCaptor<NetworkAttributesParcelable> mNapCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ private void startIpMemoryStore(boolean supplyService) {
+ if (supplyService) {
+ doAnswer(invocation -> {
+ ((IIpMemoryStoreCallbacks) invocation.getArgument(0))
+ .onIpMemoryStoreFetched(mMockService);
+ return null;
+ }).when(mModuleNetworkStackClient).fetchIpMemoryStore(any());
+ } else {
+ doNothing().when(mModuleNetworkStackClient).fetchIpMemoryStore(mCbCaptor.capture());
+ }
+ mStore = new IpMemoryStore(mMockContext) {
+ @Override
+ protected ModuleNetworkStackClient getModuleNetworkStackClient(Context ctx) {
+ return mModuleNetworkStackClient;
+ }
+ };
+ }
+
+ private static NetworkAttributes buildTestNetworkAttributes(String hint, int mtu) {
+ return new NetworkAttributes.Builder()
+ .setCluster(hint)
+ .setMtu(mtu)
+ .build();
+ }
+
+ @Test
+ public void testNetworkAttributes() throws Exception {
+ startIpMemoryStore(true /* supplyService */);
+ final String l2Key = "fakeKey";
+
+ mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+ status -> assertTrue("Store not successful : " + status.resultCode,
+ status.isSuccess()));
+ verify(mMockService, times(1)).storeNetworkAttributes(eq(l2Key),
+ mNapCaptor.capture(), any());
+ assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+
+ mStore.retrieveNetworkAttributes(l2Key,
+ (status, key, attr) -> {
+ assertTrue("Retrieve network attributes not successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(TEST_NETWORK_ATTRIBUTES, attr);
+ });
+
+ verify(mMockService, times(1)).retrieveNetworkAttributes(eq(l2Key), any());
+ }
+
+ @Test
+ public void testPrivateData() throws RemoteException {
+ startIpMemoryStore(true /* supplyService */);
+ final Blob b = new Blob();
+ b.data = TEST_BLOB_DATA;
+ final String l2Key = "fakeKey";
+
+ mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+ status -> {
+ assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+ });
+ verify(mMockService, times(1)).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+ eq(b), any());
+
+ mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+ (status, key, name, data) -> {
+ assertTrue("Retrieve blob status not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(name, TEST_DATA_NAME);
+ assertTrue(Arrays.equals(b.data, data.data));
+ });
+ verify(mMockService, times(1)).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+ eq(TEST_OTHER_DATA_NAME), any());
+ }
+
+ @Test
+ public void testFindL2Key()
+ throws UnknownHostException, RemoteException, Exception {
+ startIpMemoryStore(true /* supplyService */);
+ final String l2Key = "fakeKey";
+
+ mStore.findL2Key(TEST_NETWORK_ATTRIBUTES,
+ (status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(l2Key, key);
+ });
+ verify(mMockService, times(1)).findL2Key(mNapCaptor.capture(), any());
+ assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+ }
+
+ @Test
+ public void testIsSameNetwork() throws UnknownHostException, RemoteException {
+ startIpMemoryStore(true /* supplyService */);
+ final String l2Key1 = "fakeKey1";
+ final String l2Key2 = "fakeKey2";
+
+ mStore.isSameNetwork(l2Key1, l2Key2,
+ (status, answer) -> {
+ assertFalse("Retrieve network sameness suspiciously successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+ assertNull(answer);
+ });
+ verify(mMockService, times(1)).isSameNetwork(eq(l2Key1), eq(l2Key2), any());
+ }
+
+ @Test
+ public void testEnqueuedIpMsRequests() throws Exception {
+ startIpMemoryStore(false /* supplyService */);
+
+ final Blob b = new Blob();
+ b.data = TEST_BLOB_DATA;
+ final String l2Key = "fakeKey";
+
+ // enqueue multiple ipms requests
+ mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+ status -> assertTrue("Store not successful : " + status.resultCode,
+ status.isSuccess()));
+ mStore.retrieveNetworkAttributes(l2Key,
+ (status, key, attr) -> {
+ assertTrue("Retrieve network attributes not successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(TEST_NETWORK_ATTRIBUTES, attr);
+ });
+ mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+ status -> assertTrue("Store not successful : " + status.resultCode,
+ status.isSuccess()));
+ mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+ (status, key, name, data) -> {
+ assertTrue("Retrieve blob status not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(name, TEST_DATA_NAME);
+ assertTrue(Arrays.equals(b.data, data.data));
+ });
+
+ // get ipms service ready
+ mCbCaptor.getValue().onIpMemoryStoreFetched(mMockService);
+
+ InOrder inOrder = inOrder(mMockService);
+
+ inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(), any());
+ inOrder.verify(mMockService).retrieveNetworkAttributes(eq(l2Key), any());
+ inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+ eq(b), any());
+ inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+ eq(TEST_OTHER_DATA_NAME), any());
+ assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+ }
+
+ @Test
+ public void testEnqueuedIpMsRequestsWithException() throws Exception {
+ startIpMemoryStore(true /* supplyService */);
+ doThrow(RemoteException.class).when(mMockService).retrieveNetworkAttributes(any(), any());
+
+ final Blob b = new Blob();
+ b.data = TEST_BLOB_DATA;
+ final String l2Key = "fakeKey";
+
+ // enqueue multiple ipms requests
+ mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+ status -> assertTrue("Store not successful : " + status.resultCode,
+ status.isSuccess()));
+ mStore.retrieveNetworkAttributes(l2Key,
+ (status, key, attr) -> {
+ assertTrue("Retrieve network attributes not successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(TEST_NETWORK_ATTRIBUTES, attr);
+ });
+ mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+ status -> assertTrue("Store not successful : " + status.resultCode,
+ status.isSuccess()));
+ mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+ (status, key, name, data) -> {
+ assertTrue("Retrieve blob status not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(name, TEST_DATA_NAME);
+ assertTrue(Arrays.equals(b.data, data.data));
+ });
+
+ // verify the rest of the queue is still processed in order even if the remote exception
+ // occurs when calling one or more requests
+ InOrder inOrder = inOrder(mMockService);
+
+ inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(), any());
+ inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+ eq(b), any());
+ inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+ eq(TEST_OTHER_DATA_NAME), any());
+ assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+ }
+
+ @Test
+ public void testEnqueuedIpMsRequestsCallbackFunctionWithException() throws Exception {
+ startIpMemoryStore(true /* supplyService */);
+
+ final Blob b = new Blob();
+ b.data = TEST_BLOB_DATA;
+ final String l2Key = "fakeKey";
+
+ // enqueue multiple ipms requests
+ mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+ status -> assertTrue("Store not successful : " + status.resultCode,
+ status.isSuccess()));
+ mStore.retrieveNetworkAttributes(l2Key,
+ (status, key, attr) -> {
+ throw new RuntimeException("retrieveNetworkAttributes test");
+ });
+ mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+ status -> {
+ throw new RuntimeException("storeBlob test");
+ });
+ mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+ (status, key, name, data) -> {
+ assertTrue("Retrieve blob status not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(name, TEST_DATA_NAME);
+ assertTrue(Arrays.equals(b.data, data.data));
+ });
+
+ // verify the rest of the queue is still processed in order even if when one or more
+ // callback throw the remote exception
+ InOrder inOrder = inOrder(mMockService);
+
+ inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(),
+ any());
+ inOrder.verify(mMockService).retrieveNetworkAttributes(eq(l2Key), any());
+ inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+ eq(b), any());
+ inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+ eq(TEST_OTHER_DATA_NAME), any());
+ assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+ }
+
+ @Test
+ public void testFactoryReset() throws RemoteException {
+ startIpMemoryStore(true /* supplyService */);
+ mStore.factoryReset();
+ verify(mMockService, times(1)).factoryReset();
+ }
+}
diff --git a/tests/unit/java/android/net/IpSecAlgorithmTest.java b/tests/unit/java/android/net/IpSecAlgorithmTest.java
new file mode 100644
index 0000000..5bd2214
--- /dev/null
+++ b/tests/unit/java/android/net/IpSecAlgorithmTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.IpSecAlgorithm.ALGO_TO_REQUIRED_FIRST_SDK;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.CollectionUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map.Entry;
+import java.util.Random;
+import java.util.Set;
+
+/** Unit tests for {@link IpSecAlgorithm}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class IpSecAlgorithmTest {
+ private static final byte[] KEY_MATERIAL;
+
+ private final Resources mMockResources = mock(Resources.class);
+
+ static {
+ KEY_MATERIAL = new byte[128];
+ new Random().nextBytes(KEY_MATERIAL);
+ };
+
+ private static byte[] generateKey(int keyLenInBits) {
+ return Arrays.copyOf(KEY_MATERIAL, keyLenInBits / 8);
+ }
+
+ @Test
+ public void testNoTruncLen() throws Exception {
+ Entry<String, Integer>[] authAndAeadList =
+ new Entry[] {
+ new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_MD5, 128),
+ new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA1, 160),
+ new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA256, 256),
+ new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA384, 384),
+ new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA512, 512),
+ new SimpleEntry<>(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, 224),
+ };
+
+ // Expect auth and aead algorithms to throw errors if trunclen is omitted.
+ for (Entry<String, Integer> algData : authAndAeadList) {
+ try {
+ new IpSecAlgorithm(
+ algData.getKey(), Arrays.copyOf(KEY_MATERIAL, algData.getValue() / 8));
+ fail("Expected exception on unprovided auth trunclen");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ // Ensure crypt works with no truncation length supplied.
+ new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, Arrays.copyOf(KEY_MATERIAL, 256 / 8));
+ }
+
+ private void checkAuthKeyAndTruncLenValidation(String algoName, int keyLen, int truncLen)
+ throws Exception {
+ new IpSecAlgorithm(algoName, generateKey(keyLen), truncLen);
+
+ try {
+ new IpSecAlgorithm(algoName, generateKey(keyLen));
+ fail("Expected exception on unprovided auth trunclen");
+ } catch (IllegalArgumentException pass) {
+ }
+
+ try {
+ new IpSecAlgorithm(algoName, generateKey(keyLen + 8), truncLen);
+ fail("Invalid key length not validated");
+ } catch (IllegalArgumentException pass) {
+ }
+
+ try {
+ new IpSecAlgorithm(algoName, generateKey(keyLen), truncLen + 1);
+ fail("Invalid truncation length not validated");
+ } catch (IllegalArgumentException pass) {
+ }
+ }
+
+ private void checkCryptKeyLenValidation(String algoName, int keyLen) throws Exception {
+ new IpSecAlgorithm(algoName, generateKey(keyLen));
+
+ try {
+ new IpSecAlgorithm(algoName, generateKey(keyLen + 8));
+ fail("Invalid key length not validated");
+ } catch (IllegalArgumentException pass) {
+ }
+ }
+
+ @Test
+ public void testValidationForAlgosAddedInS() throws Exception {
+ if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.R) {
+ return;
+ }
+
+ for (int len : new int[] {160, 224, 288}) {
+ checkCryptKeyLenValidation(IpSecAlgorithm.CRYPT_AES_CTR, len);
+ }
+ checkAuthKeyAndTruncLenValidation(IpSecAlgorithm.AUTH_AES_XCBC, 128, 96);
+ checkAuthKeyAndTruncLenValidation(IpSecAlgorithm.AUTH_AES_CMAC, 128, 96);
+ checkAuthKeyAndTruncLenValidation(IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305, 288, 128);
+ }
+
+ @Test
+ public void testTruncLenValidation() throws Exception {
+ for (int truncLen : new int[] {256, 512}) {
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ Arrays.copyOf(KEY_MATERIAL, 512 / 8),
+ truncLen);
+ }
+
+ for (int truncLen : new int[] {255, 513}) {
+ try {
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ Arrays.copyOf(KEY_MATERIAL, 512 / 8),
+ truncLen);
+ fail("Invalid truncation length not validated");
+ } catch (IllegalArgumentException pass) {
+ }
+ }
+ }
+
+ @Test
+ public void testLenValidation() throws Exception {
+ for (int len : new int[] {128, 192, 256}) {
+ new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, Arrays.copyOf(KEY_MATERIAL, len / 8));
+ }
+ try {
+ new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, Arrays.copyOf(KEY_MATERIAL, 384 / 8));
+ fail("Invalid key length not validated");
+ } catch (IllegalArgumentException pass) {
+ }
+ }
+
+ @Test
+ public void testAlgoNameValidation() throws Exception {
+ try {
+ new IpSecAlgorithm("rot13", Arrays.copyOf(KEY_MATERIAL, 128 / 8));
+ fail("Invalid algorithm name not validated");
+ } catch (IllegalArgumentException pass) {
+ }
+ }
+
+ @Test
+ public void testParcelUnparcel() throws Exception {
+ IpSecAlgorithm init =
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_HMAC_SHA512, Arrays.copyOf(KEY_MATERIAL, 512 / 8), 256);
+
+ Parcel p = Parcel.obtain();
+ p.setDataPosition(0);
+ init.writeToParcel(p, 0);
+
+ p.setDataPosition(0);
+ IpSecAlgorithm fin = IpSecAlgorithm.CREATOR.createFromParcel(p);
+ assertTrue("Parcel/Unparcel failed!", IpSecAlgorithm.equals(init, fin));
+ p.recycle();
+ }
+
+ private static Set<String> getMandatoryAlgos() {
+ return CollectionUtils.filter(
+ ALGO_TO_REQUIRED_FIRST_SDK.keySet(),
+ i -> Build.VERSION.DEVICE_INITIAL_SDK_INT >= ALGO_TO_REQUIRED_FIRST_SDK.get(i));
+ }
+
+ private static Set<String> getOptionalAlgos() {
+ return CollectionUtils.filter(
+ ALGO_TO_REQUIRED_FIRST_SDK.keySet(),
+ i -> Build.VERSION.DEVICE_INITIAL_SDK_INT < ALGO_TO_REQUIRED_FIRST_SDK.get(i));
+ }
+
+ @Test
+ public void testGetSupportedAlgorithms() throws Exception {
+ assertTrue(IpSecAlgorithm.getSupportedAlgorithms().containsAll(getMandatoryAlgos()));
+ assertTrue(ALGO_TO_REQUIRED_FIRST_SDK.keySet().containsAll(
+ IpSecAlgorithm.getSupportedAlgorithms()));
+ }
+
+ @Test
+ public void testLoadAlgos() throws Exception {
+ final Set<String> optionalAlgoSet = getOptionalAlgos();
+ final String[] optionalAlgos = optionalAlgoSet.toArray(new String[0]);
+
+ doReturn(optionalAlgos).when(mMockResources)
+ .getStringArray(com.android.internal.R.array.config_optionalIpSecAlgorithms);
+
+ final Set<String> enabledAlgos = new HashSet<>(IpSecAlgorithm.loadAlgos(mMockResources));
+ final Set<String> expectedAlgos = ALGO_TO_REQUIRED_FIRST_SDK.keySet();
+
+ assertEquals(expectedAlgos, enabledAlgos);
+ }
+}
diff --git a/tests/unit/java/android/net/IpSecConfigTest.java b/tests/unit/java/android/net/IpSecConfigTest.java
new file mode 100644
index 0000000..25e225e
--- /dev/null
+++ b/tests/unit/java/android/net/IpSecConfigTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link IpSecConfig}. */
+@SmallTest
+@RunWith(JUnit4.class)
+public class IpSecConfigTest {
+
+ @Test
+ public void testDefaults() throws Exception {
+ IpSecConfig c = new IpSecConfig();
+ assertEquals(IpSecTransform.MODE_TRANSPORT, c.getMode());
+ assertEquals("", c.getSourceAddress());
+ assertEquals("", c.getDestinationAddress());
+ assertNull(c.getNetwork());
+ assertEquals(IpSecTransform.ENCAP_NONE, c.getEncapType());
+ assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getEncapSocketResourceId());
+ assertEquals(0, c.getEncapRemotePort());
+ assertEquals(0, c.getNattKeepaliveInterval());
+ assertNull(c.getEncryption());
+ assertNull(c.getAuthentication());
+ assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getSpiResourceId());
+ assertEquals(0, c.getXfrmInterfaceId());
+ }
+
+ private IpSecConfig getSampleConfig() {
+ IpSecConfig c = new IpSecConfig();
+ c.setMode(IpSecTransform.MODE_TUNNEL);
+ c.setSourceAddress("0.0.0.0");
+ c.setDestinationAddress("1.2.3.4");
+ c.setSpiResourceId(1984);
+ c.setEncryption(
+ new IpSecAlgorithm(
+ IpSecAlgorithm.CRYPT_AES_CBC,
+ new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}));
+ c.setAuthentication(
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_HMAC_MD5,
+ new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0},
+ 128));
+ c.setAuthenticatedEncryption(
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+ new byte[] {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0, 1, 2, 3, 4
+ },
+ 128));
+ c.setEncapType(android.system.OsConstants.UDP_ENCAP_ESPINUDP);
+ c.setEncapSocketResourceId(7);
+ c.setEncapRemotePort(22);
+ c.setNattKeepaliveInterval(42);
+ c.setMarkValue(12);
+ c.setMarkMask(23);
+ c.setXfrmInterfaceId(34);
+
+ return c;
+ }
+
+ @Test
+ public void testCopyConstructor() {
+ IpSecConfig original = getSampleConfig();
+ IpSecConfig copy = new IpSecConfig(original);
+
+ assertEquals(original, copy);
+ assertNotSame(original, copy);
+ }
+
+ @Test
+ public void testParcelUnparcel() {
+ assertParcelingIsLossless(new IpSecConfig());
+
+ IpSecConfig c = getSampleConfig();
+ assertParcelSane(c, 15);
+ }
+}
diff --git a/tests/unit/java/android/net/IpSecManagerTest.java b/tests/unit/java/android/net/IpSecManagerTest.java
new file mode 100644
index 0000000..730e2d5
--- /dev/null
+++ b/tests/unit/java/android/net/IpSecManagerTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.system.Os;
+import android.test.mock.MockContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.IpSecService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+/** Unit tests for {@link IpSecManager}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class IpSecManagerTest {
+
+ private static final int TEST_UDP_ENCAP_PORT = 34567;
+ private static final int DROID_SPI = 0xD1201D;
+ private static final int DUMMY_RESOURCE_ID = 0x1234;
+
+ private static final InetAddress GOOGLE_DNS_4;
+ private static final String VTI_INTF_NAME = "ipsec_test";
+ private static final InetAddress VTI_LOCAL_ADDRESS;
+ private static final LinkAddress VTI_INNER_ADDRESS = new LinkAddress("10.0.1.1/24");
+
+ static {
+ try {
+ // Google Public DNS Addresses;
+ GOOGLE_DNS_4 = InetAddress.getByName("8.8.8.8");
+ VTI_LOCAL_ADDRESS = InetAddress.getByName("8.8.4.4");
+ } catch (UnknownHostException e) {
+ throw new RuntimeException("Could not resolve DNS Addresses", e);
+ }
+ }
+
+ private IpSecService mMockIpSecService;
+ private IpSecManager mIpSecManager;
+ private MockContext mMockContext = new MockContext() {
+ @Override
+ public String getOpPackageName() {
+ return "fooPackage";
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ mMockIpSecService = mock(IpSecService.class);
+ mIpSecManager = new IpSecManager(mMockContext, mMockIpSecService);
+ }
+
+ /*
+ * Allocate a specific SPI
+ * Close SPIs
+ */
+ @Test
+ public void testAllocSpi() throws Exception {
+ IpSecSpiResponse spiResp =
+ new IpSecSpiResponse(IpSecManager.Status.OK, DUMMY_RESOURCE_ID, DROID_SPI);
+ when(mMockIpSecService.allocateSecurityParameterIndex(
+ eq(GOOGLE_DNS_4.getHostAddress()),
+ eq(DROID_SPI),
+ anyObject()))
+ .thenReturn(spiResp);
+
+ IpSecManager.SecurityParameterIndex droidSpi =
+ mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4, DROID_SPI);
+ assertEquals(DROID_SPI, droidSpi.getSpi());
+
+ droidSpi.close();
+
+ verify(mMockIpSecService).releaseSecurityParameterIndex(DUMMY_RESOURCE_ID);
+ }
+
+ @Test
+ public void testAllocRandomSpi() throws Exception {
+ IpSecSpiResponse spiResp =
+ new IpSecSpiResponse(IpSecManager.Status.OK, DUMMY_RESOURCE_ID, DROID_SPI);
+ when(mMockIpSecService.allocateSecurityParameterIndex(
+ eq(GOOGLE_DNS_4.getHostAddress()),
+ eq(IpSecManager.INVALID_SECURITY_PARAMETER_INDEX),
+ anyObject()))
+ .thenReturn(spiResp);
+
+ IpSecManager.SecurityParameterIndex randomSpi =
+ mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4);
+
+ assertEquals(DROID_SPI, randomSpi.getSpi());
+
+ randomSpi.close();
+
+ verify(mMockIpSecService).releaseSecurityParameterIndex(DUMMY_RESOURCE_ID);
+ }
+
+ /*
+ * Throws resource unavailable exception
+ */
+ @Test
+ public void testAllocSpiResUnavailableException() throws Exception {
+ IpSecSpiResponse spiResp =
+ new IpSecSpiResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE, 0, 0);
+ when(mMockIpSecService.allocateSecurityParameterIndex(
+ anyString(), anyInt(), anyObject()))
+ .thenReturn(spiResp);
+
+ try {
+ mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4);
+ fail("ResourceUnavailableException was not thrown");
+ } catch (IpSecManager.ResourceUnavailableException e) {
+ }
+ }
+
+ /*
+ * Throws spi unavailable exception
+ */
+ @Test
+ public void testAllocSpiSpiUnavailableException() throws Exception {
+ IpSecSpiResponse spiResp = new IpSecSpiResponse(IpSecManager.Status.SPI_UNAVAILABLE, 0, 0);
+ when(mMockIpSecService.allocateSecurityParameterIndex(
+ anyString(), anyInt(), anyObject()))
+ .thenReturn(spiResp);
+
+ try {
+ mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4);
+ fail("ResourceUnavailableException was not thrown");
+ } catch (IpSecManager.ResourceUnavailableException e) {
+ }
+ }
+
+ /*
+ * Should throw exception when request spi 0 in IpSecManager
+ */
+ @Test
+ public void testRequestAllocInvalidSpi() throws Exception {
+ try {
+ mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4, 0);
+ fail("Able to allocate invalid spi");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ @Test
+ public void testOpenEncapsulationSocket() throws Exception {
+ IpSecUdpEncapResponse udpEncapResp =
+ new IpSecUdpEncapResponse(
+ IpSecManager.Status.OK,
+ DUMMY_RESOURCE_ID,
+ TEST_UDP_ENCAP_PORT,
+ Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP));
+ when(mMockIpSecService.openUdpEncapsulationSocket(eq(TEST_UDP_ENCAP_PORT), anyObject()))
+ .thenReturn(udpEncapResp);
+
+ IpSecManager.UdpEncapsulationSocket encapSocket =
+ mIpSecManager.openUdpEncapsulationSocket(TEST_UDP_ENCAP_PORT);
+ assertNotNull(encapSocket.getFileDescriptor());
+ assertEquals(TEST_UDP_ENCAP_PORT, encapSocket.getPort());
+
+ encapSocket.close();
+
+ verify(mMockIpSecService).closeUdpEncapsulationSocket(DUMMY_RESOURCE_ID);
+ }
+
+ @Test
+ public void testApplyTransportModeTransformEnsuresSocketCreation() throws Exception {
+ Socket socket = new Socket();
+ IpSecConfig dummyConfig = new IpSecConfig();
+ IpSecTransform dummyTransform = new IpSecTransform(null, dummyConfig);
+
+ // Even if underlying SocketImpl is not initalized, this should force the init, and
+ // thereby succeed.
+ mIpSecManager.applyTransportModeTransform(
+ socket, IpSecManager.DIRECTION_IN, dummyTransform);
+
+ // Check to make sure the FileDescriptor is non-null
+ assertNotNull(socket.getFileDescriptor$());
+ }
+
+ @Test
+ public void testRemoveTransportModeTransformsForcesSocketCreation() throws Exception {
+ Socket socket = new Socket();
+
+ // Even if underlying SocketImpl is not initalized, this should force the init, and
+ // thereby succeed.
+ mIpSecManager.removeTransportModeTransforms(socket);
+
+ // Check to make sure the FileDescriptor is non-null
+ assertNotNull(socket.getFileDescriptor$());
+ }
+
+ @Test
+ public void testOpenEncapsulationSocketOnRandomPort() throws Exception {
+ IpSecUdpEncapResponse udpEncapResp =
+ new IpSecUdpEncapResponse(
+ IpSecManager.Status.OK,
+ DUMMY_RESOURCE_ID,
+ TEST_UDP_ENCAP_PORT,
+ Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP));
+
+ when(mMockIpSecService.openUdpEncapsulationSocket(eq(0), anyObject()))
+ .thenReturn(udpEncapResp);
+
+ IpSecManager.UdpEncapsulationSocket encapSocket =
+ mIpSecManager.openUdpEncapsulationSocket();
+
+ assertNotNull(encapSocket.getFileDescriptor());
+ assertEquals(TEST_UDP_ENCAP_PORT, encapSocket.getPort());
+
+ encapSocket.close();
+
+ verify(mMockIpSecService).closeUdpEncapsulationSocket(DUMMY_RESOURCE_ID);
+ }
+
+ @Test
+ public void testOpenEncapsulationSocketWithInvalidPort() throws Exception {
+ try {
+ mIpSecManager.openUdpEncapsulationSocket(IpSecManager.INVALID_SECURITY_PARAMETER_INDEX);
+ fail("IllegalArgumentException was not thrown");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ // TODO: add test when applicable transform builder interface is available
+
+ private IpSecManager.IpSecTunnelInterface createAndValidateVti(int resourceId, String intfName)
+ throws Exception {
+ IpSecTunnelInterfaceResponse dummyResponse =
+ new IpSecTunnelInterfaceResponse(IpSecManager.Status.OK, resourceId, intfName);
+ when(mMockIpSecService.createTunnelInterface(
+ eq(VTI_LOCAL_ADDRESS.getHostAddress()), eq(GOOGLE_DNS_4.getHostAddress()),
+ anyObject(), anyObject(), anyString()))
+ .thenReturn(dummyResponse);
+
+ IpSecManager.IpSecTunnelInterface tunnelIntf = mIpSecManager.createIpSecTunnelInterface(
+ VTI_LOCAL_ADDRESS, GOOGLE_DNS_4, mock(Network.class));
+
+ assertNotNull(tunnelIntf);
+ return tunnelIntf;
+ }
+
+ @Test
+ public void testCreateVti() throws Exception {
+ IpSecManager.IpSecTunnelInterface tunnelIntf =
+ createAndValidateVti(DUMMY_RESOURCE_ID, VTI_INTF_NAME);
+
+ assertEquals(VTI_INTF_NAME, tunnelIntf.getInterfaceName());
+
+ tunnelIntf.close();
+ verify(mMockIpSecService).deleteTunnelInterface(eq(DUMMY_RESOURCE_ID), anyString());
+ }
+
+ @Test
+ public void testAddRemoveAddressesFromVti() throws Exception {
+ IpSecManager.IpSecTunnelInterface tunnelIntf =
+ createAndValidateVti(DUMMY_RESOURCE_ID, VTI_INTF_NAME);
+
+ tunnelIntf.addAddress(VTI_INNER_ADDRESS.getAddress(),
+ VTI_INNER_ADDRESS.getPrefixLength());
+ verify(mMockIpSecService)
+ .addAddressToTunnelInterface(
+ eq(DUMMY_RESOURCE_ID), eq(VTI_INNER_ADDRESS), anyString());
+
+ tunnelIntf.removeAddress(VTI_INNER_ADDRESS.getAddress(),
+ VTI_INNER_ADDRESS.getPrefixLength());
+ verify(mMockIpSecService)
+ .addAddressToTunnelInterface(
+ eq(DUMMY_RESOURCE_ID), eq(VTI_INNER_ADDRESS), anyString());
+ }
+}
diff --git a/tests/unit/java/android/net/IpSecTransformTest.java b/tests/unit/java/android/net/IpSecTransformTest.java
new file mode 100644
index 0000000..424f23d
--- /dev/null
+++ b/tests/unit/java/android/net/IpSecTransformTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link IpSecTransform}. */
+@SmallTest
+@RunWith(JUnit4.class)
+public class IpSecTransformTest {
+
+ @Test
+ public void testCreateTransformCopiesConfig() {
+ // Create a config with a few parameters to make sure it's not empty
+ IpSecConfig config = new IpSecConfig();
+ config.setSourceAddress("0.0.0.0");
+ config.setDestinationAddress("1.2.3.4");
+ config.setSpiResourceId(1984);
+
+ IpSecTransform preModification = new IpSecTransform(null, config);
+
+ config.setSpiResourceId(1985);
+ IpSecTransform postModification = new IpSecTransform(null, config);
+
+ assertNotEquals(preModification, postModification);
+ }
+
+ @Test
+ public void testCreateTransformsWithSameConfigEqual() {
+ // Create a config with a few parameters to make sure it's not empty
+ IpSecConfig config = new IpSecConfig();
+ config.setSourceAddress("0.0.0.0");
+ config.setDestinationAddress("1.2.3.4");
+ config.setSpiResourceId(1984);
+
+ IpSecTransform config1 = new IpSecTransform(null, config);
+ IpSecTransform config2 = new IpSecTransform(null, config);
+
+ assertEquals(config1, config2);
+ }
+}
diff --git a/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java b/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java
new file mode 100644
index 0000000..fc739fb
--- /dev/null
+++ b/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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;
+
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.util.KeepalivePacketDataUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+@RunWith(JUnit4.class)
+public final class KeepalivePacketDataUtilTest {
+ private static final byte[] IPV4_KEEPALIVE_SRC_ADDR = {10, 0, 0, 1};
+ private static final byte[] IPV4_KEEPALIVE_DST_ADDR = {10, 0, 0, 5};
+
+ @Before
+ public void setUp() {}
+
+ @Test
+ public void testFromTcpKeepaliveStableParcelable() throws Exception {
+ final int srcPort = 1234;
+ final int dstPort = 4321;
+ final int seq = 0x11111111;
+ final int ack = 0x22222222;
+ final int wnd = 8000;
+ final int wndScale = 2;
+ final int tos = 4;
+ final int ttl = 64;
+ TcpKeepalivePacketData resultData = null;
+ final TcpKeepalivePacketDataParcelable testInfo = new TcpKeepalivePacketDataParcelable();
+ testInfo.srcAddress = IPV4_KEEPALIVE_SRC_ADDR;
+ testInfo.srcPort = srcPort;
+ testInfo.dstAddress = IPV4_KEEPALIVE_DST_ADDR;
+ testInfo.dstPort = dstPort;
+ testInfo.seq = seq;
+ testInfo.ack = ack;
+ testInfo.rcvWnd = wnd;
+ testInfo.rcvWndScale = wndScale;
+ testInfo.tos = tos;
+ testInfo.ttl = ttl;
+ try {
+ resultData = KeepalivePacketDataUtil.fromStableParcelable(testInfo);
+ } catch (InvalidPacketException e) {
+ fail("InvalidPacketException: " + e);
+ }
+
+ assertEquals(InetAddress.getByAddress(testInfo.srcAddress), resultData.getSrcAddress());
+ assertEquals(InetAddress.getByAddress(testInfo.dstAddress), resultData.getDstAddress());
+ assertEquals(testInfo.srcPort, resultData.getSrcPort());
+ assertEquals(testInfo.dstPort, resultData.getDstPort());
+ assertEquals(testInfo.seq, resultData.tcpSeq);
+ assertEquals(testInfo.ack, resultData.tcpAck);
+ assertEquals(testInfo.rcvWnd, resultData.tcpWindow);
+ assertEquals(testInfo.rcvWndScale, resultData.tcpWindowScale);
+ assertEquals(testInfo.tos, resultData.ipTos);
+ assertEquals(testInfo.ttl, resultData.ipTtl);
+
+ assertParcelingIsLossless(resultData);
+
+ final byte[] packet = resultData.getPacket();
+ // IP version and IHL
+ assertEquals(packet[0], 0x45);
+ // TOS
+ assertEquals(packet[1], tos);
+ // TTL
+ assertEquals(packet[8], ttl);
+ // Source IP address.
+ byte[] ip = new byte[4];
+ ByteBuffer buf = ByteBuffer.wrap(packet, 12, 4);
+ buf.get(ip);
+ assertArrayEquals(ip, IPV4_KEEPALIVE_SRC_ADDR);
+ // Destination IP address.
+ buf = ByteBuffer.wrap(packet, 16, 4);
+ buf.get(ip);
+ assertArrayEquals(ip, IPV4_KEEPALIVE_DST_ADDR);
+
+ buf = ByteBuffer.wrap(packet, 20, 12);
+ // Source port.
+ assertEquals(buf.getShort(), srcPort);
+ // Destination port.
+ assertEquals(buf.getShort(), dstPort);
+ // Sequence number.
+ assertEquals(buf.getInt(), seq);
+ // Ack.
+ assertEquals(buf.getInt(), ack);
+ // Window size.
+ buf = ByteBuffer.wrap(packet, 34, 2);
+ assertEquals(buf.getShort(), wnd >> wndScale);
+ }
+
+ //TODO: add ipv6 test when ipv6 supported
+
+ @Test
+ public void testToTcpKeepaliveStableParcelable() throws Exception {
+ final int srcPort = 1234;
+ final int dstPort = 4321;
+ final int sequence = 0x11111111;
+ final int ack = 0x22222222;
+ final int wnd = 48_000;
+ final int wndScale = 2;
+ final int tos = 4;
+ final int ttl = 64;
+ final TcpKeepalivePacketDataParcelable testInfo = new TcpKeepalivePacketDataParcelable();
+ testInfo.srcAddress = IPV4_KEEPALIVE_SRC_ADDR;
+ testInfo.srcPort = srcPort;
+ testInfo.dstAddress = IPV4_KEEPALIVE_DST_ADDR;
+ testInfo.dstPort = dstPort;
+ testInfo.seq = sequence;
+ testInfo.ack = ack;
+ testInfo.rcvWnd = wnd;
+ testInfo.rcvWndScale = wndScale;
+ testInfo.tos = tos;
+ testInfo.ttl = ttl;
+ TcpKeepalivePacketData testData = null;
+ TcpKeepalivePacketDataParcelable resultData = null;
+ testData = KeepalivePacketDataUtil.fromStableParcelable(testInfo);
+ resultData = KeepalivePacketDataUtil.toStableParcelable(testData);
+ assertArrayEquals(resultData.srcAddress, IPV4_KEEPALIVE_SRC_ADDR);
+ assertArrayEquals(resultData.dstAddress, IPV4_KEEPALIVE_DST_ADDR);
+ assertEquals(resultData.srcPort, srcPort);
+ assertEquals(resultData.dstPort, dstPort);
+ assertEquals(resultData.seq, sequence);
+ assertEquals(resultData.ack, ack);
+ assertEquals(resultData.rcvWnd, wnd);
+ assertEquals(resultData.rcvWndScale, wndScale);
+ assertEquals(resultData.tos, tos);
+ assertEquals(resultData.ttl, ttl);
+
+ final String expected = ""
+ + "android.net.TcpKeepalivePacketDataParcelable{srcAddress: [10, 0, 0, 1],"
+ + " srcPort: 1234, dstAddress: [10, 0, 0, 5], dstPort: 4321, seq: 286331153,"
+ + " ack: 572662306, rcvWnd: 48000, rcvWndScale: 2, tos: 4, ttl: 64}";
+ assertEquals(expected, resultData.toString());
+ }
+
+ @Test
+ public void testParseTcpKeepalivePacketData() throws Exception {
+ final int srcPort = 1234;
+ final int dstPort = 4321;
+ final int sequence = 0x11111111;
+ final int ack = 0x22222222;
+ final int wnd = 4800;
+ final int wndScale = 2;
+ final int tos = 4;
+ final int ttl = 64;
+ final TcpKeepalivePacketDataParcelable testParcel = new TcpKeepalivePacketDataParcelable();
+ testParcel.srcAddress = IPV4_KEEPALIVE_SRC_ADDR;
+ testParcel.srcPort = srcPort;
+ testParcel.dstAddress = IPV4_KEEPALIVE_DST_ADDR;
+ testParcel.dstPort = dstPort;
+ testParcel.seq = sequence;
+ testParcel.ack = ack;
+ testParcel.rcvWnd = wnd;
+ testParcel.rcvWndScale = wndScale;
+ testParcel.tos = tos;
+ testParcel.ttl = ttl;
+
+ final KeepalivePacketData testData =
+ KeepalivePacketDataUtil.fromStableParcelable(testParcel);
+ final TcpKeepalivePacketDataParcelable parsedParcelable =
+ KeepalivePacketDataUtil.parseTcpKeepalivePacketData(testData);
+ final TcpKeepalivePacketData roundTripData =
+ KeepalivePacketDataUtil.fromStableParcelable(parsedParcelable);
+
+ // Generated packet is the same, but rcvWnd / wndScale will differ if scale is non-zero
+ assertTrue(testData.getPacket().length > 0);
+ assertArrayEquals(testData.getPacket(), roundTripData.getPacket());
+
+ testParcel.rcvWndScale = 0;
+ final KeepalivePacketData noScaleTestData =
+ KeepalivePacketDataUtil.fromStableParcelable(testParcel);
+ final TcpKeepalivePacketDataParcelable noScaleParsedParcelable =
+ KeepalivePacketDataUtil.parseTcpKeepalivePacketData(noScaleTestData);
+ final TcpKeepalivePacketData noScaleRoundTripData =
+ KeepalivePacketDataUtil.fromStableParcelable(noScaleParsedParcelable);
+ assertEquals(noScaleTestData, noScaleRoundTripData);
+ assertTrue(noScaleTestData.getPacket().length > 0);
+ assertArrayEquals(noScaleTestData.getPacket(), noScaleRoundTripData.getPacket());
+ }
+}
diff --git a/tests/unit/java/android/net/MacAddressTest.java b/tests/unit/java/android/net/MacAddressTest.java
new file mode 100644
index 0000000..6de31f6
--- /dev/null
+++ b/tests/unit/java/android/net/MacAddressTest.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static org.junit.Assert.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 androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.MacAddressUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.util.Arrays;
+import java.util.Random;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MacAddressTest {
+
+ static class AddrTypeTestCase {
+ byte[] addr;
+ int expectedType;
+
+ static AddrTypeTestCase of(int expectedType, int... addr) {
+ AddrTypeTestCase t = new AddrTypeTestCase();
+ t.expectedType = expectedType;
+ t.addr = toByteArray(addr);
+ return t;
+ }
+ }
+
+ @Test
+ public void testMacAddrTypes() {
+ AddrTypeTestCase[] testcases = {
+ AddrTypeTestCase.of(MacAddress.TYPE_UNKNOWN),
+ AddrTypeTestCase.of(MacAddress.TYPE_UNKNOWN, 0),
+ AddrTypeTestCase.of(MacAddress.TYPE_UNKNOWN, 1, 2, 3, 4, 5),
+ AddrTypeTestCase.of(MacAddress.TYPE_UNKNOWN, 1, 2, 3, 4, 5, 6, 7),
+ AddrTypeTestCase.of(MacAddress.TYPE_UNICAST, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0),
+ AddrTypeTestCase.of(MacAddress.TYPE_BROADCAST, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff),
+ AddrTypeTestCase.of(MacAddress.TYPE_MULTICAST, 1, 2, 3, 4, 5, 6),
+ AddrTypeTestCase.of(MacAddress.TYPE_MULTICAST, 11, 22, 33, 44, 55, 66),
+ AddrTypeTestCase.of(MacAddress.TYPE_MULTICAST, 33, 33, 0xaa, 0xbb, 0xcc, 0xdd)
+ };
+
+ for (AddrTypeTestCase t : testcases) {
+ int got = MacAddress.macAddressType(t.addr);
+ String msg = String.format("expected type of %s to be %s, but got %s",
+ Arrays.toString(t.addr), t.expectedType, got);
+ assertEquals(msg, t.expectedType, got);
+
+ if (got != MacAddress.TYPE_UNKNOWN) {
+ assertEquals(got, MacAddress.fromBytes(t.addr).getAddressType());
+ }
+ }
+ }
+
+ @Test
+ public void testToOuiString() {
+ String[][] macs = {
+ {"07:00:d3:56:8a:c4", "07:00:d3"},
+ {"33:33:aa:bb:cc:dd", "33:33:aa"},
+ {"06:00:00:00:00:00", "06:00:00"},
+ {"07:00:d3:56:8a:c4", "07:00:d3"}
+ };
+
+ for (String[] pair : macs) {
+ String mac = pair[0];
+ String expected = pair[1];
+ assertEquals(expected, MacAddress.fromString(mac).toOuiString());
+ }
+ }
+
+ @Test
+ public void testHexPaddingWhenPrinting() {
+ String[] macs = {
+ "07:00:d3:56:8a:c4",
+ "33:33:aa:bb:cc:dd",
+ "06:00:00:00:00:00",
+ "07:00:d3:56:8a:c4"
+ };
+
+ for (String mac : macs) {
+ assertEquals(mac, MacAddress.fromString(mac).toString());
+ assertEquals(mac,
+ MacAddress.stringAddrFromByteAddr(MacAddress.byteAddrFromStringAddr(mac)));
+ }
+ }
+
+ @Test
+ public void testIsMulticastAddress() {
+ MacAddress[] multicastAddresses = {
+ MacAddress.BROADCAST_ADDRESS,
+ MacAddress.fromString("07:00:d3:56:8a:c4"),
+ MacAddress.fromString("33:33:aa:bb:cc:dd"),
+ };
+ MacAddress[] unicastAddresses = {
+ MacAddress.ALL_ZEROS_ADDRESS,
+ MacAddress.fromString("00:01:44:55:66:77"),
+ MacAddress.fromString("08:00:22:33:44:55"),
+ MacAddress.fromString("06:00:00:00:00:00"),
+ };
+
+ for (MacAddress mac : multicastAddresses) {
+ String msg = mac.toString() + " expected to be a multicast address";
+ assertTrue(msg, MacAddressUtils.isMulticastAddress(mac));
+ }
+ for (MacAddress mac : unicastAddresses) {
+ String msg = mac.toString() + " expected not to be a multicast address";
+ assertFalse(msg, MacAddressUtils.isMulticastAddress(mac));
+ }
+ }
+
+ @Test
+ public void testIsLocallyAssignedAddress() {
+ MacAddress[] localAddresses = {
+ MacAddress.fromString("06:00:00:00:00:00"),
+ MacAddress.fromString("07:00:d3:56:8a:c4"),
+ MacAddress.fromString("33:33:aa:bb:cc:dd"),
+ };
+ MacAddress[] universalAddresses = {
+ MacAddress.fromString("00:01:44:55:66:77"),
+ MacAddress.fromString("08:00:22:33:44:55"),
+ };
+
+ for (MacAddress mac : localAddresses) {
+ String msg = mac.toString() + " expected to be a locally assigned address";
+ assertTrue(msg, mac.isLocallyAssigned());
+ }
+ for (MacAddress mac : universalAddresses) {
+ String msg = mac.toString() + " expected not to be globally unique address";
+ assertFalse(msg, mac.isLocallyAssigned());
+ }
+ }
+
+ @Test
+ public void testMacAddressConversions() {
+ final int iterations = 10000;
+ for (int i = 0; i < iterations; i++) {
+ MacAddress mac = MacAddressUtils.createRandomUnicastAddress();
+
+ String stringRepr = mac.toString();
+ byte[] bytesRepr = mac.toByteArray();
+
+ assertEquals(mac, MacAddress.fromString(stringRepr));
+ assertEquals(mac, MacAddress.fromBytes(bytesRepr));
+
+ assertEquals(mac, MacAddress.fromString(MacAddress.stringAddrFromByteAddr(bytesRepr)));
+ assertEquals(mac, MacAddress.fromBytes(MacAddress.byteAddrFromStringAddr(stringRepr)));
+ }
+ }
+
+ @Test
+ public void testMacAddressRandomGeneration() {
+ final int iterations = 1000;
+ final String expectedAndroidOui = "da:a1:19";
+ for (int i = 0; i < iterations; i++) {
+ MacAddress mac = MacAddress.createRandomUnicastAddressWithGoogleBase();
+ String stringRepr = mac.toString();
+
+ assertTrue(stringRepr + " expected to be a locally assigned address",
+ mac.isLocallyAssigned());
+ assertTrue(stringRepr + " expected to begin with " + expectedAndroidOui,
+ stringRepr.startsWith(expectedAndroidOui));
+ }
+
+ final Random r = new Random();
+ final String anotherOui = "24:5f:78";
+ final String expectedLocalOui = "26:5f:78";
+ final MacAddress base = MacAddress.fromString(anotherOui + ":0:0:0");
+ for (int i = 0; i < iterations; i++) {
+ MacAddress mac = MacAddressUtils.createRandomUnicastAddress(base, r);
+ String stringRepr = mac.toString();
+
+ assertTrue(stringRepr + " expected to be a locally assigned address",
+ mac.isLocallyAssigned());
+ assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType());
+ assertTrue(stringRepr + " expected to begin with " + expectedLocalOui,
+ stringRepr.startsWith(expectedLocalOui));
+ }
+
+ for (int i = 0; i < iterations; i++) {
+ MacAddress mac = MacAddressUtils.createRandomUnicastAddress();
+ String stringRepr = mac.toString();
+
+ assertTrue(stringRepr + " expected to be a locally assigned address",
+ mac.isLocallyAssigned());
+ assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType());
+ }
+ }
+
+ @Test
+ public void testConstructorInputValidation() {
+ String[] invalidStringAddresses = {
+ "",
+ "abcd",
+ "1:2:3:4:5",
+ "1:2:3:4:5:6:7",
+ "10000:2:3:4:5:6",
+ };
+
+ for (String s : invalidStringAddresses) {
+ try {
+ MacAddress mac = MacAddress.fromString(s);
+ fail("MacAddress.fromString(" + s + ") should have failed, but returned " + mac);
+ } catch (IllegalArgumentException excepted) {
+ }
+ }
+
+ try {
+ MacAddress mac = MacAddress.fromString(null);
+ fail("MacAddress.fromString(null) should have failed, but returned " + mac);
+ } catch (NullPointerException excepted) {
+ }
+
+ byte[][] invalidBytesAddresses = {
+ {},
+ {1,2,3,4,5},
+ {1,2,3,4,5,6,7},
+ };
+
+ for (byte[] b : invalidBytesAddresses) {
+ try {
+ MacAddress mac = MacAddress.fromBytes(b);
+ fail("MacAddress.fromBytes(" + Arrays.toString(b)
+ + ") should have failed, but returned " + mac);
+ } catch (IllegalArgumentException excepted) {
+ }
+ }
+
+ try {
+ MacAddress mac = MacAddress.fromBytes(null);
+ fail("MacAddress.fromBytes(null) should have failed, but returned " + mac);
+ } catch (NullPointerException excepted) {
+ }
+ }
+
+ @Test
+ public void testMatches() {
+ // match 4 bytes prefix
+ assertTrue(MacAddress.fromString("aa:bb:cc:dd:ee:11").matches(
+ MacAddress.fromString("aa:bb:cc:dd:00:00"),
+ MacAddress.fromString("ff:ff:ff:ff:00:00")));
+
+ // match bytes 0,1,2 and 5
+ assertTrue(MacAddress.fromString("aa:bb:cc:dd:ee:11").matches(
+ MacAddress.fromString("aa:bb:cc:00:00:11"),
+ MacAddress.fromString("ff:ff:ff:00:00:ff")));
+
+ // match 34 bit prefix
+ assertTrue(MacAddress.fromString("aa:bb:cc:dd:ee:11").matches(
+ MacAddress.fromString("aa:bb:cc:dd:c0:00"),
+ MacAddress.fromString("ff:ff:ff:ff:c0:00")));
+
+ // fail to match 36 bit prefix
+ assertFalse(MacAddress.fromString("aa:bb:cc:dd:ee:11").matches(
+ MacAddress.fromString("aa:bb:cc:dd:40:00"),
+ MacAddress.fromString("ff:ff:ff:ff:f0:00")));
+
+ // match all 6 bytes
+ assertTrue(MacAddress.fromString("aa:bb:cc:dd:ee:11").matches(
+ MacAddress.fromString("aa:bb:cc:dd:ee:11"),
+ MacAddress.fromString("ff:ff:ff:ff:ff:ff")));
+
+ // match none of 6 bytes
+ assertTrue(MacAddress.fromString("aa:bb:cc:dd:ee:11").matches(
+ MacAddress.fromString("00:00:00:00:00:00"),
+ MacAddress.fromString("00:00:00:00:00:00")));
+ }
+
+ /**
+ * Tests that link-local address generation from MAC is valid.
+ */
+ @Test
+ public void testLinkLocalFromMacGeneration() {
+ MacAddress mac = MacAddress.fromString("52:74:f2:b1:a8:7f");
+ byte[] inet6ll = {(byte) 0xfe, (byte) 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x74,
+ (byte) 0xf2, (byte) 0xff, (byte) 0xfe, (byte) 0xb1, (byte) 0xa8, 0x7f};
+ Inet6Address llv6 = mac.getLinkLocalIpv6FromEui48Mac();
+ assertTrue(llv6.isLinkLocalAddress());
+ assertArrayEquals(inet6ll, llv6.getAddress());
+ }
+
+ static byte[] toByteArray(int... in) {
+ byte[] out = new byte[in.length];
+ for (int i = 0; i < in.length; i++) {
+ out[i] = (byte) in[i];
+ }
+ return out;
+ }
+}
diff --git a/tests/unit/java/android/net/NetworkIdentityTest.kt b/tests/unit/java/android/net/NetworkIdentityTest.kt
new file mode 100644
index 0000000..eb2b85c
--- /dev/null
+++ b/tests/unit/java/android/net/NetworkIdentityTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net
+
+import android.net.NetworkIdentity.OEM_NONE
+import android.net.NetworkIdentity.OEM_PAID
+import android.net.NetworkIdentity.OEM_PRIVATE
+import android.net.NetworkIdentity.getOemBitfield
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.test.assertEquals
+
+@RunWith(JUnit4::class)
+class NetworkIdentityTest {
+ @Test
+ fun testGetOemBitfield() {
+ val oemNone = NetworkCapabilities().apply {
+ setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID, false)
+ setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, false)
+ }
+ val oemPaid = NetworkCapabilities().apply {
+ setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID, true)
+ setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, false)
+ }
+ val oemPrivate = NetworkCapabilities().apply {
+ setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID, false)
+ setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, true)
+ }
+ val oemAll = NetworkCapabilities().apply {
+ setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID, true)
+ setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, true)
+ }
+
+ assertEquals(getOemBitfield(oemNone), OEM_NONE)
+ assertEquals(getOemBitfield(oemPaid), OEM_PAID)
+ assertEquals(getOemBitfield(oemPrivate), OEM_PRIVATE)
+ assertEquals(getOemBitfield(oemAll), OEM_PAID or OEM_PRIVATE)
+ }
+}
diff --git a/tests/unit/java/android/net/NetworkStatsHistoryTest.java b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
new file mode 100644
index 0000000..13558cd
--- /dev/null
+++ b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
@@ -0,0 +1,599 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLong;
+import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLong;
+import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
+import static android.net.NetworkStatsHistory.FIELD_ALL;
+import static android.net.NetworkStatsHistory.FIELD_OPERATIONS;
+import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
+import static android.net.NetworkStatsHistory.FIELD_RX_PACKETS;
+import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
+import static android.net.TrafficStats.GB_IN_BYTES;
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+import static android.text.format.DateUtils.YEAR_IN_MILLIS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.tests.net.R;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.util.Random;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkStatsHistoryTest {
+ private static final String TAG = "NetworkStatsHistoryTest";
+
+ private static final long TEST_START = 1194220800000L;
+
+ private NetworkStatsHistory stats;
+
+ @After
+ public void tearDown() throws Exception {
+ if (stats != null) {
+ assertConsistent(stats);
+ }
+ }
+
+ @Test
+ public void testReadOriginalVersion() throws Exception {
+ final Context context = InstrumentationRegistry.getContext();
+ final DataInputStream in =
+ new DataInputStream(context.getResources().openRawResource(R.raw.history_v1));
+
+ NetworkStatsHistory.Entry entry = null;
+ try {
+ final NetworkStatsHistory history = new NetworkStatsHistory(in);
+ assertEquals(15 * SECOND_IN_MILLIS, history.getBucketDuration());
+
+ entry = history.getValues(0, entry);
+ assertEquals(29143L, entry.rxBytes);
+ assertEquals(6223L, entry.txBytes);
+
+ entry = history.getValues(history.size() - 1, entry);
+ assertEquals(1476L, entry.rxBytes);
+ assertEquals(838L, entry.txBytes);
+
+ entry = history.getValues(Long.MIN_VALUE, Long.MAX_VALUE, entry);
+ assertEquals(332401L, entry.rxBytes);
+ assertEquals(64314L, entry.txBytes);
+
+ } finally {
+ in.close();
+ }
+ }
+
+ @Test
+ public void testRecordSingleBucket() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ // record data into narrow window to get single bucket
+ stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+
+ assertEquals(1, stats.size());
+ assertValues(stats, 0, SECOND_IN_MILLIS, 1024L, 10L, 2048L, 20L, 2L);
+ }
+
+ @Test
+ public void testRecordEqualBuckets() throws Exception {
+ final long bucketDuration = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(bucketDuration);
+
+ // split equally across two buckets
+ final long recordStart = TEST_START + (bucketDuration / 2);
+ stats.recordData(recordStart, recordStart + bucketDuration,
+ new NetworkStats.Entry(1024L, 10L, 128L, 2L, 2L));
+
+ assertEquals(2, stats.size());
+ assertValues(stats, 0, HOUR_IN_MILLIS / 2, 512L, 5L, 64L, 1L, 1L);
+ assertValues(stats, 1, HOUR_IN_MILLIS / 2, 512L, 5L, 64L, 1L, 1L);
+ }
+
+ @Test
+ public void testRecordTouchingBuckets() throws Exception {
+ final long BUCKET_SIZE = 15 * MINUTE_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ // split almost completely into middle bucket, but with a few minutes
+ // overlap into neighboring buckets. total record is 20 minutes.
+ final long recordStart = (TEST_START + BUCKET_SIZE) - MINUTE_IN_MILLIS;
+ final long recordEnd = (TEST_START + (BUCKET_SIZE * 2)) + (MINUTE_IN_MILLIS * 4);
+ stats.recordData(recordStart, recordEnd,
+ new NetworkStats.Entry(1000L, 2000L, 5000L, 10000L, 100L));
+
+ assertEquals(3, stats.size());
+ // first bucket should have (1/20 of value)
+ assertValues(stats, 0, MINUTE_IN_MILLIS, 50L, 100L, 250L, 500L, 5L);
+ // second bucket should have (15/20 of value)
+ assertValues(stats, 1, 15 * MINUTE_IN_MILLIS, 750L, 1500L, 3750L, 7500L, 75L);
+ // final bucket should have (4/20 of value)
+ assertValues(stats, 2, 4 * MINUTE_IN_MILLIS, 200L, 400L, 1000L, 2000L, 20L);
+ }
+
+ @Test
+ public void testRecordGapBuckets() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ // record some data today and next week with large gap
+ final long firstStart = TEST_START;
+ final long lastStart = TEST_START + WEEK_IN_MILLIS;
+ stats.recordData(firstStart, firstStart + SECOND_IN_MILLIS,
+ new NetworkStats.Entry(128L, 2L, 256L, 4L, 1L));
+ stats.recordData(lastStart, lastStart + SECOND_IN_MILLIS,
+ new NetworkStats.Entry(64L, 1L, 512L, 8L, 2L));
+
+ // we should have two buckets, far apart from each other
+ assertEquals(2, stats.size());
+ assertValues(stats, 0, SECOND_IN_MILLIS, 128L, 2L, 256L, 4L, 1L);
+ assertValues(stats, 1, SECOND_IN_MILLIS, 64L, 1L, 512L, 8L, 2L);
+
+ // now record something in middle, spread across two buckets
+ final long middleStart = TEST_START + DAY_IN_MILLIS;
+ final long middleEnd = middleStart + (HOUR_IN_MILLIS * 2);
+ stats.recordData(middleStart, middleEnd,
+ new NetworkStats.Entry(2048L, 4L, 2048L, 4L, 2L));
+
+ // now should have four buckets, with new record in middle two buckets
+ assertEquals(4, stats.size());
+ assertValues(stats, 0, SECOND_IN_MILLIS, 128L, 2L, 256L, 4L, 1L);
+ assertValues(stats, 1, HOUR_IN_MILLIS, 1024L, 2L, 1024L, 2L, 1L);
+ assertValues(stats, 2, HOUR_IN_MILLIS, 1024L, 2L, 1024L, 2L, 1L);
+ assertValues(stats, 3, SECOND_IN_MILLIS, 64L, 1L, 512L, 8L, 2L);
+ }
+
+ @Test
+ public void testRecordOverlapBuckets() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ // record some data in one bucket, and another overlapping buckets
+ stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS,
+ new NetworkStats.Entry(256L, 2L, 256L, 2L, 1L));
+ final long midStart = TEST_START + (HOUR_IN_MILLIS / 2);
+ stats.recordData(midStart, midStart + HOUR_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 1024L, 10L, 10L));
+
+ // should have two buckets, with some data mixed together
+ assertEquals(2, stats.size());
+ assertValues(stats, 0, SECOND_IN_MILLIS + (HOUR_IN_MILLIS / 2), 768L, 7L, 768L, 7L, 6L);
+ assertValues(stats, 1, (HOUR_IN_MILLIS / 2), 512L, 5L, 512L, 5L, 5L);
+ }
+
+ @Test
+ public void testRecordEntireGapIdentical() throws Exception {
+ // first, create two separate histories far apart
+ final NetworkStatsHistory stats1 = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ stats1.recordData(TEST_START, TEST_START + 2 * HOUR_IN_MILLIS, 2000L, 1000L);
+
+ final long TEST_START_2 = TEST_START + DAY_IN_MILLIS;
+ final NetworkStatsHistory stats2 = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ stats2.recordData(TEST_START_2, TEST_START_2 + 2 * HOUR_IN_MILLIS, 1000L, 500L);
+
+ // combine together with identical bucket size
+ stats = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ stats.recordEntireHistory(stats1);
+ stats.recordEntireHistory(stats2);
+
+ // first verify that totals match up
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 3000L, 1500L);
+
+ // now inspect internal buckets
+ assertValues(stats, 0, 1000L, 500L);
+ assertValues(stats, 1, 1000L, 500L);
+ assertValues(stats, 2, 500L, 250L);
+ assertValues(stats, 3, 500L, 250L);
+ }
+
+ @Test
+ public void testRecordEntireOverlapVaryingBuckets() throws Exception {
+ // create history just over hour bucket boundary
+ final NetworkStatsHistory stats1 = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ stats1.recordData(TEST_START, TEST_START + MINUTE_IN_MILLIS * 60, 600L, 600L);
+
+ final long TEST_START_2 = TEST_START + MINUTE_IN_MILLIS;
+ final NetworkStatsHistory stats2 = new NetworkStatsHistory(MINUTE_IN_MILLIS);
+ stats2.recordData(TEST_START_2, TEST_START_2 + MINUTE_IN_MILLIS * 5, 50L, 50L);
+
+ // combine together with minute bucket size
+ stats = new NetworkStatsHistory(MINUTE_IN_MILLIS);
+ stats.recordEntireHistory(stats1);
+ stats.recordEntireHistory(stats2);
+
+ // first verify that totals match up
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 650L, 650L);
+
+ // now inspect internal buckets
+ assertValues(stats, 0, 10L, 10L);
+ assertValues(stats, 1, 20L, 20L);
+ assertValues(stats, 2, 20L, 20L);
+ assertValues(stats, 3, 20L, 20L);
+ assertValues(stats, 4, 20L, 20L);
+ assertValues(stats, 5, 20L, 20L);
+ assertValues(stats, 6, 10L, 10L);
+
+ // now combine using 15min buckets
+ stats = new NetworkStatsHistory(HOUR_IN_MILLIS / 4);
+ stats.recordEntireHistory(stats1);
+ stats.recordEntireHistory(stats2);
+
+ // first verify that totals match up
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 650L, 650L);
+
+ // and inspect buckets
+ assertValues(stats, 0, 200L, 200L);
+ assertValues(stats, 1, 150L, 150L);
+ assertValues(stats, 2, 150L, 150L);
+ assertValues(stats, 3, 150L, 150L);
+ }
+
+ @Test
+ public void testRemove() throws Exception {
+ stats = new NetworkStatsHistory(HOUR_IN_MILLIS);
+
+ // record some data across 24 buckets
+ stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 24L, 24L);
+ assertEquals(24, stats.size());
+
+ // try removing invalid data; should be no change
+ stats.removeBucketsBefore(0 - DAY_IN_MILLIS);
+ assertEquals(24, stats.size());
+
+ // try removing far before buckets; should be no change
+ stats.removeBucketsBefore(TEST_START - YEAR_IN_MILLIS);
+ assertEquals(24, stats.size());
+
+ // try removing just moments into first bucket; should be no change
+ // since that bucket contains data beyond the cutoff
+ stats.removeBucketsBefore(TEST_START + SECOND_IN_MILLIS);
+ assertEquals(24, stats.size());
+
+ // try removing single bucket
+ stats.removeBucketsBefore(TEST_START + HOUR_IN_MILLIS);
+ assertEquals(23, stats.size());
+
+ // try removing multiple buckets
+ stats.removeBucketsBefore(TEST_START + (4 * HOUR_IN_MILLIS));
+ assertEquals(20, stats.size());
+
+ // try removing all buckets
+ stats.removeBucketsBefore(TEST_START + YEAR_IN_MILLIS);
+ assertEquals(0, stats.size());
+ }
+
+ @Test
+ public void testTotalData() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ // record uniform data across day
+ stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 2400L, 4800L);
+
+ // verify that total outside range is 0
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START - DAY_IN_MILLIS, 0L, 0L);
+
+ // verify total in first hour
+ assertValues(stats, TEST_START, TEST_START + HOUR_IN_MILLIS, 100L, 200L);
+
+ // verify total across 1.5 hours
+ assertValues(stats, TEST_START, TEST_START + (long) (1.5 * HOUR_IN_MILLIS), 150L, 300L);
+
+ // verify total beyond end
+ assertValues(stats, TEST_START + (23 * HOUR_IN_MILLIS), TEST_START + WEEK_IN_MILLIS, 100L, 200L);
+
+ // verify everything total
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 2400L, 4800L);
+
+ }
+
+ @Test
+ public void testFuzzing() throws Exception {
+ try {
+ // fuzzing with random events, looking for crashes
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ final Random r = new Random();
+ for (int i = 0; i < 500; i++) {
+ stats = new NetworkStatsHistory(r.nextLong());
+ for (int j = 0; j < 10000; j++) {
+ if (r.nextBoolean()) {
+ // add range
+ final long start = r.nextLong();
+ final long end = start + r.nextInt();
+ entry.rxBytes = nextPositiveLong(r);
+ entry.rxPackets = nextPositiveLong(r);
+ entry.txBytes = nextPositiveLong(r);
+ entry.txPackets = nextPositiveLong(r);
+ entry.operations = nextPositiveLong(r);
+ stats.recordData(start, end, entry);
+ } else {
+ // trim something
+ stats.removeBucketsBefore(r.nextLong());
+ }
+ }
+ assertConsistent(stats);
+ }
+ } catch (Throwable e) {
+ Log.e(TAG, String.valueOf(stats));
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static long nextPositiveLong(Random r) {
+ final long value = r.nextLong();
+ return value < 0 ? -value : value;
+ }
+
+ @Test
+ public void testIgnoreFields() throws Exception {
+ final NetworkStatsHistory history = new NetworkStatsHistory(
+ MINUTE_IN_MILLIS, 0, FIELD_RX_BYTES | FIELD_TX_BYTES);
+
+ history.recordData(0, MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+ history.recordData(0, 2 * MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(2L, 2L, 2L, 2L, 2L));
+
+ assertFullValues(history, UNKNOWN, 1026L, UNKNOWN, 2050L, UNKNOWN, UNKNOWN);
+ }
+
+ @Test
+ public void testIgnoreFieldsRecordIn() throws Exception {
+ final NetworkStatsHistory full = new NetworkStatsHistory(MINUTE_IN_MILLIS, 0, FIELD_ALL);
+ final NetworkStatsHistory partial = new NetworkStatsHistory(
+ MINUTE_IN_MILLIS, 0, FIELD_RX_PACKETS | FIELD_OPERATIONS);
+
+ full.recordData(0, MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+ partial.recordEntireHistory(full);
+
+ assertFullValues(partial, UNKNOWN, UNKNOWN, 10L, UNKNOWN, UNKNOWN, 4L);
+ }
+
+ @Test
+ public void testIgnoreFieldsRecordOut() throws Exception {
+ final NetworkStatsHistory full = new NetworkStatsHistory(MINUTE_IN_MILLIS, 0, FIELD_ALL);
+ final NetworkStatsHistory partial = new NetworkStatsHistory(
+ MINUTE_IN_MILLIS, 0, FIELD_RX_PACKETS | FIELD_OPERATIONS);
+
+ partial.recordData(0, MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+ full.recordEntireHistory(partial);
+
+ assertFullValues(full, MINUTE_IN_MILLIS, 0L, 10L, 0L, 0L, 4L);
+ }
+
+ @Test
+ public void testSerialize() throws Exception {
+ final NetworkStatsHistory before = new NetworkStatsHistory(MINUTE_IN_MILLIS, 40, FIELD_ALL);
+ before.recordData(0, 4 * MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+ before.recordData(DAY_IN_MILLIS, DAY_IN_MILLIS + MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(10L, 20L, 30L, 40L, 50L));
+
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ before.writeToStream(new DataOutputStream(out));
+ out.close();
+
+ final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ final NetworkStatsHistory after = new NetworkStatsHistory(new DataInputStream(in));
+
+ // must have identical totals before and after
+ assertFullValues(before, 5 * MINUTE_IN_MILLIS, 1034L, 30L, 2078L, 60L, 54L);
+ assertFullValues(after, 5 * MINUTE_IN_MILLIS, 1034L, 30L, 2078L, 60L, 54L);
+ }
+
+ @Test
+ public void testVarLong() throws Exception {
+ assertEquals(0L, performVarLong(0L));
+ assertEquals(-1L, performVarLong(-1L));
+ assertEquals(1024L, performVarLong(1024L));
+ assertEquals(-1024L, performVarLong(-1024L));
+ assertEquals(40 * MB_IN_BYTES, performVarLong(40 * MB_IN_BYTES));
+ assertEquals(512 * GB_IN_BYTES, performVarLong(512 * GB_IN_BYTES));
+ assertEquals(Long.MIN_VALUE, performVarLong(Long.MIN_VALUE));
+ assertEquals(Long.MAX_VALUE, performVarLong(Long.MAX_VALUE));
+ assertEquals(Long.MIN_VALUE + 40, performVarLong(Long.MIN_VALUE + 40));
+ assertEquals(Long.MAX_VALUE - 40, performVarLong(Long.MAX_VALUE - 40));
+ }
+
+ @Test
+ public void testIndexBeforeAfter() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ final long FIRST_START = TEST_START;
+ final long FIRST_END = FIRST_START + (2 * HOUR_IN_MILLIS);
+ final long SECOND_START = TEST_START + WEEK_IN_MILLIS;
+ final long SECOND_END = SECOND_START + HOUR_IN_MILLIS;
+ final long THIRD_START = TEST_START + (2 * WEEK_IN_MILLIS);
+ final long THIRD_END = THIRD_START + (2 * HOUR_IN_MILLIS);
+
+ stats.recordData(FIRST_START, FIRST_END,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+ stats.recordData(SECOND_START, SECOND_END,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+ stats.recordData(THIRD_START, THIRD_END,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+
+ // should have buckets: 2+1+2
+ assertEquals(5, stats.size());
+
+ assertIndexBeforeAfter(stats, 0, 0, Long.MIN_VALUE);
+ assertIndexBeforeAfter(stats, 0, 1, FIRST_START);
+ assertIndexBeforeAfter(stats, 0, 1, FIRST_START + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 0, 2, FIRST_START + HOUR_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 1, 2, FIRST_START + HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 1, 2, FIRST_END - MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 1, 2, FIRST_END);
+ assertIndexBeforeAfter(stats, 1, 2, FIRST_END + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 1, 2, SECOND_START - MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 1, 3, SECOND_START);
+ assertIndexBeforeAfter(stats, 2, 3, SECOND_END);
+ assertIndexBeforeAfter(stats, 2, 3, SECOND_END + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 2, 3, THIRD_START - MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 2, 4, THIRD_START);
+ assertIndexBeforeAfter(stats, 3, 4, THIRD_START + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 3, 4, THIRD_START + HOUR_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 4, 4, THIRD_END);
+ assertIndexBeforeAfter(stats, 4, 4, THIRD_END + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 4, 4, Long.MAX_VALUE);
+ }
+
+ @Test
+ public void testIntersects() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ final long FIRST_START = TEST_START;
+ final long FIRST_END = FIRST_START + (2 * HOUR_IN_MILLIS);
+ final long SECOND_START = TEST_START + WEEK_IN_MILLIS;
+ final long SECOND_END = SECOND_START + HOUR_IN_MILLIS;
+ final long THIRD_START = TEST_START + (2 * WEEK_IN_MILLIS);
+ final long THIRD_END = THIRD_START + (2 * HOUR_IN_MILLIS);
+
+ stats.recordData(FIRST_START, FIRST_END,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+ stats.recordData(SECOND_START, SECOND_END,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+ stats.recordData(THIRD_START, THIRD_END,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+
+ assertFalse(stats.intersects(10, 20));
+ assertFalse(stats.intersects(TEST_START + YEAR_IN_MILLIS, TEST_START + YEAR_IN_MILLIS + 1));
+ assertFalse(stats.intersects(Long.MAX_VALUE, Long.MIN_VALUE));
+
+ assertTrue(stats.intersects(Long.MIN_VALUE, Long.MAX_VALUE));
+ assertTrue(stats.intersects(10, TEST_START + YEAR_IN_MILLIS));
+ assertTrue(stats.intersects(TEST_START, TEST_START));
+ assertTrue(stats.intersects(TEST_START + DAY_IN_MILLIS, TEST_START + DAY_IN_MILLIS + 1));
+ assertTrue(stats.intersects(TEST_START + DAY_IN_MILLIS, Long.MAX_VALUE));
+ assertTrue(stats.intersects(TEST_START + 1, Long.MAX_VALUE));
+
+ assertFalse(stats.intersects(Long.MIN_VALUE, TEST_START - 1));
+ assertTrue(stats.intersects(Long.MIN_VALUE, TEST_START));
+ assertTrue(stats.intersects(Long.MIN_VALUE, TEST_START + 1));
+ }
+
+ @Test
+ public void testSetValues() throws Exception {
+ stats = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ stats.recordData(TEST_START, TEST_START + 1,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+
+ assertEquals(1024L + 2048L, stats.getTotalBytes());
+
+ final NetworkStatsHistory.Entry entry = stats.getValues(0, null);
+ entry.rxBytes /= 2;
+ entry.txBytes *= 2;
+ stats.setValues(0, entry);
+
+ assertEquals(512L + 4096L, stats.getTotalBytes());
+ }
+
+ private static void assertIndexBeforeAfter(
+ NetworkStatsHistory stats, int before, int after, long time) {
+ assertEquals("unexpected before", before, stats.getIndexBefore(time));
+ assertEquals("unexpected after", after, stats.getIndexAfter(time));
+ }
+
+ private static long performVarLong(long before) throws Exception {
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writeVarLong(new DataOutputStream(out), before);
+
+ final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ return readVarLong(new DataInputStream(in));
+ }
+
+ private static void assertConsistent(NetworkStatsHistory stats) {
+ // verify timestamps are monotonic
+ long lastStart = Long.MIN_VALUE;
+ NetworkStatsHistory.Entry entry = null;
+ for (int i = 0; i < stats.size(); i++) {
+ entry = stats.getValues(i, entry);
+ assertTrue(lastStart < entry.bucketStart);
+ lastStart = entry.bucketStart;
+ }
+ }
+
+ private static void assertValues(
+ NetworkStatsHistory stats, int index, long rxBytes, long txBytes) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(index, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ }
+
+ private static void assertValues(
+ NetworkStatsHistory stats, long start, long end, long rxBytes, long txBytes) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ }
+
+ private static void assertValues(NetworkStatsHistory stats, int index, long activeTime,
+ long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(index, null);
+ assertEquals("unexpected activeTime", activeTime, entry.activeTime);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ assertEquals("unexpected operations", operations, entry.operations);
+ }
+
+ private static void assertFullValues(NetworkStatsHistory stats, long activeTime, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, long operations) {
+ assertValues(stats, Long.MIN_VALUE, Long.MAX_VALUE, activeTime, rxBytes, rxPackets, txBytes,
+ txPackets, operations);
+ }
+
+ private static void assertValues(NetworkStatsHistory stats, long start, long end,
+ long activeTime, long rxBytes, long rxPackets, long txBytes, long txPackets,
+ long operations) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null);
+ assertEquals("unexpected activeTime", activeTime, entry.activeTime);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ assertEquals("unexpected operations", operations, entry.operations);
+ }
+}
diff --git a/tests/unit/java/android/net/NetworkStatsTest.java b/tests/unit/java/android/net/NetworkStatsTest.java
new file mode 100644
index 0000000..23d5a7e
--- /dev/null
+++ b/tests/unit/java/android/net/NetworkStatsTest.java
@@ -0,0 +1,1024 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.INTERFACES_ALL;
+import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkStats.ROAMING_ALL;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.ROAMING_YES;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DBG_VPN_IN;
+import static android.net.NetworkStats.SET_DBG_VPN_OUT;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_ALL;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Process;
+import android.util.ArrayMap;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.android.collect.Sets;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkStatsTest {
+
+ private static final String TEST_IFACE = "test0";
+ private static final String TEST_IFACE2 = "test2";
+ private static final int TEST_UID = 1001;
+ private static final long TEST_START = 1194220800000L;
+
+ @Test
+ public void testFindIndex() throws Exception {
+ final NetworkStats stats = new NetworkStats(TEST_START, 5)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 1024L, 8L, 0L, 0L, 10)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 0L, 0L, 1024L, 8L, 11)
+ .insertEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 12)
+ .insertEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+ DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12);
+
+ assertEquals(4, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES,
+ ROAMING_YES, DEFAULT_NETWORK_YES));
+ assertEquals(3, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO));
+ assertEquals(2, stats.findIndex(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES,
+ ROAMING_NO, DEFAULT_NETWORK_YES));
+ assertEquals(1, stats.findIndex(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO));
+ assertEquals(0, stats.findIndex(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_YES));
+ assertEquals(-1, stats.findIndex(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO));
+ assertEquals(-1, stats.findIndex(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO));
+ }
+
+ @Test
+ public void testFindIndexHinted() {
+ final NetworkStats stats = new NetworkStats(TEST_START, 3)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 1024L, 8L, 0L, 0L, 10)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11)
+ .insertEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12)
+ .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1024L, 8L, 0L, 0L, 10)
+ .insertEntry(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 0L, 0L, 1024L, 8L, 11)
+ .insertEntry(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11)
+ .insertEntry(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12)
+ .insertEntry(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+ DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 12);
+
+ // verify that we correctly find across regardless of hinting
+ for (int hint = 0; hint < stats.size(); hint++) {
+ assertEquals(0, stats.findIndexHinted(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, hint));
+ assertEquals(1, stats.findIndexHinted(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, hint));
+ assertEquals(2, stats.findIndexHinted(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, hint));
+ assertEquals(3, stats.findIndexHinted(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, hint));
+ assertEquals(4, stats.findIndexHinted(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, hint));
+ assertEquals(5, stats.findIndexHinted(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D,
+ METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, hint));
+ assertEquals(6, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, hint));
+ assertEquals(7, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE,
+ METERED_YES, ROAMING_YES, DEFAULT_NETWORK_NO, hint));
+ assertEquals(-1, stats.findIndexHinted(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, hint));
+ assertEquals(-1, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE,
+ METERED_YES, ROAMING_YES, DEFAULT_NETWORK_YES, hint));
+ }
+ }
+
+ @Test
+ public void testAddEntryGrow() throws Exception {
+ final NetworkStats stats = new NetworkStats(TEST_START, 4);
+
+ assertEquals(0, stats.size());
+ assertEquals(4, stats.internalSize());
+
+ stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 1L, 1L, 2L, 2L, 3);
+ stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 2L, 2L, 2L, 2L, 4);
+ stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+ DEFAULT_NETWORK_YES, 3L, 3L, 2L, 2L, 5);
+ stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+ DEFAULT_NETWORK_NO, 3L, 3L, 2L, 2L, 5);
+
+ assertEquals(4, stats.size());
+ assertEquals(4, stats.internalSize());
+
+ stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 4L, 40L, 4L, 40L, 7);
+ stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 5L, 50L, 4L, 40L, 8);
+ stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 6L, 60L, 5L, 50L, 10);
+ stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+ DEFAULT_NETWORK_YES, 7L, 70L, 5L, 50L, 11);
+ stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+ DEFAULT_NETWORK_NO, 7L, 70L, 5L, 50L, 11);
+
+ assertEquals(9, stats.size());
+ assertTrue(stats.internalSize() >= 9);
+
+ assertValues(stats, 0, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 1L, 1L, 2L, 2L, 3);
+ assertValues(stats, 1, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 2L, 2L, 2L, 2L, 4);
+ assertValues(stats, 2, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+ DEFAULT_NETWORK_YES, 3L, 3L, 2L, 2L, 5);
+ assertValues(stats, 3, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES,
+ ROAMING_YES, DEFAULT_NETWORK_NO, 3L, 3L, 2L, 2L, 5);
+ assertValues(stats, 4, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 4L, 40L, 4L, 40L, 7);
+ assertValues(stats, 5, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 5L, 50L, 4L, 40L, 8);
+ assertValues(stats, 6, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 6L, 60L, 5L, 50L, 10);
+ assertValues(stats, 7, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+ DEFAULT_NETWORK_YES, 7L, 70L, 5L, 50L, 11);
+ assertValues(stats, 8, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES,
+ ROAMING_YES, DEFAULT_NETWORK_NO, 7L, 70L, 5L, 50L, 11);
+ }
+
+ @Test
+ public void testCombineExisting() throws Exception {
+ final NetworkStats stats = new NetworkStats(TEST_START, 10);
+
+ stats.insertEntry(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 10);
+ stats.insertEntry(TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2);
+ stats.combineValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, -128L, -1L,
+ -128L, -1L, -1);
+
+ assertValues(stats, 0, TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 384L, 3L, 128L, 1L, 9);
+ assertValues(stats, 1, TEST_IFACE, 1001, SET_DEFAULT, 0xff, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 128L, 1L, 128L, 1L, 2);
+
+ // now try combining that should create row
+ stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 3);
+ assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 128L, 1L, 128L, 1L, 3);
+ stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 3);
+ assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 256L, 2L, 256L, 2L, 6);
+ }
+
+ @Test
+ public void testSubtractIdenticalData() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 2)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+
+ final NetworkStats after = new NetworkStats(TEST_START, 2)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+
+ final NetworkStats result = after.subtract(before);
+
+ // identical data should result in zero delta
+ assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0);
+ assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0);
+ }
+
+ @Test
+ public void testSubtractIdenticalRows() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 2)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+
+ final NetworkStats after = new NetworkStats(TEST_START, 2)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1025L, 9L, 2L, 1L, 15)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 1028L, 9L, 20);
+
+ final NetworkStats result = after.subtract(before);
+
+ // expect delta between measurements
+ assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1L, 1L, 2L, 1L, 4);
+ assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 3L, 1L, 4L, 1L, 8);
+ }
+
+ @Test
+ public void testSubtractNewRows() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 2)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+
+ final NetworkStats after = new NetworkStats(TEST_START, 3)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12)
+ .insertEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20);
+
+ final NetworkStats result = after.subtract(before);
+
+ // its okay to have new rows
+ assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0);
+ assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0);
+ assertValues(result, 2, TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 20);
+ }
+
+ @Test
+ public void testSubtractMissingRows() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 2)
+ .insertEntry(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 0L, 0L, 0L, 0)
+ .insertEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2048L, 0L, 0L, 0L, 0);
+
+ final NetworkStats after = new NetworkStats(TEST_START, 1)
+ .insertEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2049L, 2L, 3L, 4L, 0);
+
+ final NetworkStats result = after.subtract(before);
+
+ // should silently drop omitted rows
+ assertEquals(1, result.size());
+ assertValues(result, 0, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 1L, 2L, 3L, 4L, 0);
+ assertEquals(4L, result.getTotalBytes());
+ }
+
+ @Test
+ public void testTotalBytes() throws Exception {
+ final NetworkStats iface = new NetworkStats(TEST_START, 2)
+ .insertEntry(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 128L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 256L, 0L, 0L, 0L, 0L);
+ assertEquals(384L, iface.getTotalBytes());
+
+ final NetworkStats uidSet = new NetworkStats(TEST_START, 3)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+ assertEquals(96L, uidSet.getTotalBytes());
+
+ final NetworkStats uidTag = new NetworkStats(TEST_START, 6)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L);
+ assertEquals(64L, uidTag.getTotalBytes());
+
+ final NetworkStats uidMetered = new NetworkStats(TEST_START, 3)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
+ assertEquals(96L, uidMetered.getTotalBytes());
+
+ final NetworkStats uidRoaming = new NetworkStats(TEST_START, 3)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+ DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
+ assertEquals(96L, uidRoaming.getTotalBytes());
+ }
+
+ @Test
+ public void testGroupedByIfaceEmpty() throws Exception {
+ final NetworkStats uidStats = new NetworkStats(TEST_START, 3);
+ final NetworkStats grouped = uidStats.groupedByIface();
+
+ assertEquals(0, uidStats.size());
+ assertEquals(0, grouped.size());
+ }
+
+ @Test
+ public void testGroupedByIfaceAll() throws Exception {
+ final NetworkStats uidStats = new NetworkStats(TEST_START, 3)
+ .insertEntry(IFACE_ALL, 100, SET_ALL, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L)
+ .insertEntry(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 128L, 8L, 0L, 2L, 20L)
+ .insertEntry(IFACE_ALL, 101, SET_ALL, TAG_NONE, METERED_NO, ROAMING_YES,
+ DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L);
+ final NetworkStats grouped = uidStats.groupedByIface();
+
+ assertEquals(3, uidStats.size());
+ assertEquals(1, grouped.size());
+
+ assertValues(grouped, 0, IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, 384L, 24L, 0L, 6L, 0L);
+ }
+
+ @Test
+ public void testGroupedByIface() throws Exception {
+ final NetworkStats uidStats = new NetworkStats(TEST_START, 7)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L)
+ .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 64L, 4L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+ DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L);
+
+ final NetworkStats grouped = uidStats.groupedByIface();
+
+ assertEquals(7, uidStats.size());
+
+ assertEquals(2, grouped.size());
+ assertValues(grouped, 0, TEST_IFACE, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, 384L, 24L, 0L, 2L, 0L);
+ assertValues(grouped, 1, TEST_IFACE2, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, 1024L, 64L, 0L, 0L, 0L);
+ }
+
+ @Test
+ public void testAddAllValues() {
+ final NetworkStats first = new NetworkStats(TEST_START, 5)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
+ DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
+
+ final NetworkStats second = new NetworkStats(TEST_START, 2)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
+ DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
+
+ first.combineAllValues(second);
+
+ assertEquals(4, first.size());
+ assertValues(first, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 64L, 0L, 0L, 0L, 0L);
+ assertValues(first, 1, TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L);
+ assertValues(first, 2, TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
+ DEFAULT_NETWORK_YES, 64L, 0L, 0L, 0L, 0L);
+ assertValues(first, 3, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L);
+ }
+
+ @Test
+ public void testGetTotal() {
+ final NetworkStats stats = new NetworkStats(TEST_START, 7)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L)
+ .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 64L, 4L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 512L,32L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+ DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L);
+
+ assertValues(stats.getTotal(null), 1408L, 88L, 0L, 2L, 20L);
+ assertValues(stats.getTotal(null, 100), 1280L, 80L, 0L, 2L, 20L);
+ assertValues(stats.getTotal(null, 101), 128L, 8L, 0L, 0L, 0L);
+
+ final HashSet<String> ifaces = Sets.newHashSet();
+ assertValues(stats.getTotal(null, ifaces), 0L, 0L, 0L, 0L, 0L);
+
+ ifaces.add(TEST_IFACE2);
+ assertValues(stats.getTotal(null, ifaces), 1024L, 64L, 0L, 0L, 0L);
+ }
+
+ @Test
+ public void testRemoveUids() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 3);
+
+ // Test 0 item stats.
+ NetworkStats after = before.clone();
+ after.removeUids(new int[0]);
+ assertEquals(0, after.size());
+ after.removeUids(new int[] {100});
+ assertEquals(0, after.size());
+
+ // Test 1 item stats.
+ before.insertEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, 1L, 128L, 0L, 2L, 20L);
+ after = before.clone();
+ after.removeUids(new int[0]);
+ assertEquals(1, after.size());
+ assertValues(after, 0, TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L);
+ after.removeUids(new int[] {99});
+ assertEquals(0, after.size());
+
+ // Append remaining test items.
+ before.insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L)
+ .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 16L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 8L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 4L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 64L, 2L, 0L, 0L, 0L);
+ assertEquals(7, before.size());
+
+ // Test remove with empty uid list.
+ after = before.clone();
+ after.removeUids(new int[0]);
+ assertValues(after.getTotalIncludingTags(null), 127L, 254L, 0L, 4L, 40L);
+
+ // Test remove uids don't exist in stats.
+ after.removeUids(new int[] {98, 0, Integer.MIN_VALUE, Integer.MAX_VALUE});
+ assertValues(after.getTotalIncludingTags(null), 127L, 254L, 0L, 4L, 40L);
+
+ // Test remove all uids.
+ after.removeUids(new int[] {99, 100, 100, 101});
+ assertEquals(0, after.size());
+
+ // Test remove in the middle.
+ after = before.clone();
+ after.removeUids(new int[] {100});
+ assertEquals(3, after.size());
+ assertValues(after, 0, TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L);
+ assertValues(after, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 32L, 4L, 0L, 0L, 0L);
+ assertValues(after, 2, TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 64L, 2L, 0L, 0L, 0L);
+ }
+
+ @Test
+ public void testRemoveEmptyEntries() throws Exception {
+ // Test empty stats.
+ final NetworkStats statsEmpty = new NetworkStats(TEST_START, 3);
+ assertEquals(0, statsEmpty.removeEmptyEntries().size());
+
+ // Test stats with non-zero entry.
+ final NetworkStats statsNonZero = new NetworkStats(TEST_START, 1)
+ .insertEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L);
+ assertEquals(1, statsNonZero.size());
+ final NetworkStats expectedNonZero = statsNonZero.removeEmptyEntries();
+ assertEquals(1, expectedNonZero.size());
+ assertValues(expectedNonZero, 0, TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L);
+
+ // Test stats with empty entry.
+ final NetworkStats statsZero = new NetworkStats(TEST_START, 1)
+ .insertEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
+ assertEquals(1, statsZero.size());
+ final NetworkStats expectedZero = statsZero.removeEmptyEntries();
+ assertEquals(1, statsZero.size()); // Assert immutable.
+ assertEquals(0, expectedZero.size());
+
+ // Test stats with multiple entries.
+ final NetworkStats statsMultiple = new NetworkStats(TEST_START, 0)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L)
+ .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 0L, 8L, 0L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 4L, 0L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 2L, 0L)
+ .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 0L, 1L);
+ assertEquals(9, statsMultiple.size());
+ final NetworkStats expectedMultiple = statsMultiple.removeEmptyEntries();
+ assertEquals(9, statsMultiple.size()); // Assert immutable.
+ assertEquals(7, expectedMultiple.size());
+ assertValues(expectedMultiple.getTotalIncludingTags(null), 14L, 104L, 4L, 4L, 21L);
+
+ // Test stats with multiple empty entries.
+ assertEquals(statsMultiple.size(), statsMultiple.subtract(statsMultiple).size());
+ assertEquals(0, statsMultiple.subtract(statsMultiple).removeEmptyEntries().size());
+ }
+
+ @Test
+ public void testClone() throws Exception {
+ final NetworkStats original = new NetworkStats(TEST_START, 5)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+ .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
+
+ // make clone and mutate original
+ final NetworkStats clone = original.clone();
+ original.insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L);
+
+ assertEquals(3, original.size());
+ assertEquals(2, clone.size());
+
+ assertEquals(128L + 512L + 128L, original.getTotalBytes());
+ assertEquals(128L + 512L, clone.getTotalBytes());
+ }
+
+ @Test
+ public void testAddWhenEmpty() throws Exception {
+ final NetworkStats red = new NetworkStats(TEST_START, -1);
+ final NetworkStats blue = new NetworkStats(TEST_START, 5)
+ .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+ .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
+
+ // We're mostly checking that we don't crash
+ red.combineAllValues(blue);
+ }
+
+ @Test
+ public void testMigrateTun() throws Exception {
+ final int tunUid = 10030;
+ final String tunIface = "tun0";
+ final String underlyingIface = "wlan0";
+ final int testTag1 = 8888;
+ NetworkStats delta = new NetworkStats(TEST_START, 17)
+ .insertEntry(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 39605L, 46L, 12259L, 55L, 0L)
+ .insertEntry(tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
+ .insertEntry(tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 72667L, 197L, 43909L, 241L, 0L)
+ .insertEntry(tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 9297L, 17L, 4128L, 21L, 0L)
+ // VPN package also uses some traffic through unprotected network.
+ .insertEntry(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 4983L, 10L, 1801L, 12L, 0L)
+ .insertEntry(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
+ // Tag entries
+ .insertEntry(tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 21691L, 41L, 13820L, 51L, 0L)
+ .insertEntry(tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1281L, 2L, 665L, 2L, 0L)
+ // Irrelevant entries
+ .insertEntry(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1685L, 5L, 2070L, 6L, 0L)
+ // Underlying Iface entries
+ .insertEntry(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 5178L, 8L, 2139L, 11L, 0L)
+ .insertEntry(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
+ .insertEntry(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 149873L, 287L, 59217L /* smaller than sum(tun0) */,
+ 299L /* smaller than sum(tun0) */, 0L)
+ .insertEntry(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
+
+ delta.migrateTun(tunUid, tunIface, Arrays.asList(underlyingIface));
+ assertEquals(20, delta.size());
+
+ // tunIface and TEST_IFACE entries are not changed.
+ assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 39605L, 46L, 12259L, 55L, 0L);
+ assertValues(delta, 1, tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
+ assertValues(delta, 2, tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 72667L, 197L, 43909L, 241L, 0L);
+ assertValues(delta, 3, tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 9297L, 17L, 4128L, 21L, 0L);
+ assertValues(delta, 4, tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 4983L, 10L, 1801L, 12L, 0L);
+ assertValues(delta, 5, tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
+ assertValues(delta, 6, tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 21691L, 41L, 13820L, 51L, 0L);
+ assertValues(delta, 7, tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1281L, 2L, 665L, 2L, 0L);
+ assertValues(delta, 8, TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1685L, 5L, 2070L, 6L, 0L);
+
+ // Existing underlying Iface entries are updated
+ assertValues(delta, 9, underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 44783L, 54L, 14178L, 62L, 0L);
+ assertValues(delta, 10, underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
+
+ // VPN underlying Iface entries are updated
+ assertValues(delta, 11, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 28304L, 27L, 1L, 2L, 0L);
+ assertValues(delta, 12, underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
+
+ // New entries are added for new application's underlying Iface traffic
+ assertContains(delta, underlyingIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 72667L, 197L, 43123L, 227L, 0L);
+ assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 9297L, 17L, 4054, 19L, 0L);
+ assertContains(delta, underlyingIface, 10120, SET_DEFAULT, testTag1, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 21691L, 41L, 13572L, 48L, 0L);
+ assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, testTag1, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 1281L, 2L, 653L, 1L, 0L);
+
+ // New entries are added for debug purpose
+ assertContains(delta, underlyingIface, 10100, SET_DBG_VPN_IN, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 39605L, 46L, 12039, 51, 0);
+ assertContains(delta, underlyingIface, 10120, SET_DBG_VPN_IN, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 81964, 214, 47177, 246, 0);
+ assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_OUT, TAG_NONE, METERED_ALL,
+ ROAMING_ALL, DEFAULT_NETWORK_ALL, 121569, 260, 59216, 297, 0);
+
+ }
+
+ // Tests a case where all of the data received by the tun0 interface is echo back into the tun0
+ // interface by the vpn app before it's sent out of the underlying interface. The VPN app should
+ // not be charged for the echoed data but it should still be charged for any extra data it sends
+ // via the underlying interface.
+ @Test
+ public void testMigrateTun_VpnAsLoopback() {
+ final int tunUid = 10030;
+ final String tunIface = "tun0";
+ final String underlyingIface = "wlan0";
+ NetworkStats delta = new NetworkStats(TEST_START, 9)
+ // 2 different apps sent/receive data via tun0.
+ .insertEntry(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L)
+ .insertEntry(tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L)
+ // VPN package resends data through the tunnel (with exaggerated overhead)
+ .insertEntry(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 240000, 100L, 120000L, 60L, 0L)
+ // 1 app already has some traffic on the underlying interface, the other doesn't yet
+ .insertEntry(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1000L, 10L, 2000L, 20L, 0L)
+ // Traffic through the underlying interface via the vpn app.
+ // This test should redistribute this data correctly.
+ .insertEntry(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 75500L, 37L, 130000L, 70L, 0L);
+
+ delta.migrateTun(tunUid, tunIface, Arrays.asList(underlyingIface));
+ assertEquals(9, delta.size());
+
+ // tunIface entries should not be changed.
+ assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+ assertValues(delta, 1, tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L);
+ assertValues(delta, 2, tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 240000L, 100L, 120000L, 60L, 0L);
+
+ // Existing underlying Iface entries are updated
+ assertValues(delta, 3, underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 51000L, 35L, 102000L, 70L, 0L);
+
+ // VPN underlying Iface entries are updated
+ assertValues(delta, 4, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 25000L, 10L, 29800L, 15L, 0L);
+
+ // New entries are added for new application's underlying Iface traffic
+ assertContains(delta, underlyingIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L);
+
+ // New entries are added for debug purpose
+ assertContains(delta, underlyingIface, 10100, SET_DBG_VPN_IN, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+ assertContains(delta, underlyingIface, 20100, SET_DBG_VPN_IN, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, 500, 2L, 200L, 5L, 0L);
+ assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_OUT, TAG_NONE, METERED_ALL,
+ ROAMING_ALL, DEFAULT_NETWORK_ALL, 50500L, 27L, 100200L, 55, 0);
+ }
+
+ @Test
+ public void testFilter_NoFilter() {
+ NetworkStats.Entry entry1 = new NetworkStats.Entry(
+ "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry2 = new NetworkStats.Entry(
+ "test2", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry3 = new NetworkStats.Entry(
+ "test2", 10101, SET_DEFAULT, 123, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats stats = new NetworkStats(TEST_START, 3)
+ .insertEntry(entry1)
+ .insertEntry(entry2)
+ .insertEntry(entry3);
+
+ stats.filter(UID_ALL, INTERFACES_ALL, TAG_ALL);
+ assertEquals(3, stats.size());
+ assertEquals(entry1, stats.getValues(0, null));
+ assertEquals(entry2, stats.getValues(1, null));
+ assertEquals(entry3, stats.getValues(2, null));
+ }
+
+ @Test
+ public void testFilter_UidFilter() {
+ final int testUid = 10101;
+ NetworkStats.Entry entry1 = new NetworkStats.Entry(
+ "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry2 = new NetworkStats.Entry(
+ "test2", testUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry3 = new NetworkStats.Entry(
+ "test2", testUid, SET_DEFAULT, 123, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats stats = new NetworkStats(TEST_START, 3)
+ .insertEntry(entry1)
+ .insertEntry(entry2)
+ .insertEntry(entry3);
+
+ stats.filter(testUid, INTERFACES_ALL, TAG_ALL);
+ assertEquals(2, stats.size());
+ assertEquals(entry2, stats.getValues(0, null));
+ assertEquals(entry3, stats.getValues(1, null));
+ }
+
+ @Test
+ public void testFilter_InterfaceFilter() {
+ final String testIf1 = "testif1";
+ final String testIf2 = "testif2";
+ NetworkStats.Entry entry1 = new NetworkStats.Entry(
+ testIf1, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry2 = new NetworkStats.Entry(
+ "otherif", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry3 = new NetworkStats.Entry(
+ testIf1, 10101, SET_DEFAULT, 123, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry4 = new NetworkStats.Entry(
+ testIf2, 10101, SET_DEFAULT, 123, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats stats = new NetworkStats(TEST_START, 4)
+ .insertEntry(entry1)
+ .insertEntry(entry2)
+ .insertEntry(entry3)
+ .insertEntry(entry4);
+
+ stats.filter(UID_ALL, new String[] { testIf1, testIf2 }, TAG_ALL);
+ assertEquals(3, stats.size());
+ assertEquals(entry1, stats.getValues(0, null));
+ assertEquals(entry3, stats.getValues(1, null));
+ assertEquals(entry4, stats.getValues(2, null));
+ }
+
+ @Test
+ public void testFilter_EmptyInterfaceFilter() {
+ NetworkStats.Entry entry1 = new NetworkStats.Entry(
+ "if1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry2 = new NetworkStats.Entry(
+ "if2", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats stats = new NetworkStats(TEST_START, 3)
+ .insertEntry(entry1)
+ .insertEntry(entry2);
+
+ stats.filter(UID_ALL, new String[] { }, TAG_ALL);
+ assertEquals(0, stats.size());
+ }
+
+ @Test
+ public void testFilter_TagFilter() {
+ final int testTag = 123;
+ final int otherTag = 456;
+ NetworkStats.Entry entry1 = new NetworkStats.Entry(
+ "test1", 10100, SET_DEFAULT, testTag, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry2 = new NetworkStats.Entry(
+ "test2", 10101, SET_DEFAULT, testTag, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry3 = new NetworkStats.Entry(
+ "test2", 10101, SET_DEFAULT, otherTag, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats stats = new NetworkStats(TEST_START, 3)
+ .insertEntry(entry1)
+ .insertEntry(entry2)
+ .insertEntry(entry3);
+
+ stats.filter(UID_ALL, INTERFACES_ALL, testTag);
+ assertEquals(2, stats.size());
+ assertEquals(entry1, stats.getValues(0, null));
+ assertEquals(entry2, stats.getValues(1, null));
+ }
+
+ @Test
+ public void testFilterDebugEntries() {
+ NetworkStats.Entry entry1 = new NetworkStats.Entry(
+ "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry2 = new NetworkStats.Entry(
+ "test2", 10101, SET_DBG_VPN_IN, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry3 = new NetworkStats.Entry(
+ "test2", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry4 = new NetworkStats.Entry(
+ "test2", 10101, SET_DBG_VPN_OUT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats stats = new NetworkStats(TEST_START, 4)
+ .insertEntry(entry1)
+ .insertEntry(entry2)
+ .insertEntry(entry3)
+ .insertEntry(entry4);
+
+ stats.filterDebugEntries();
+
+ assertEquals(2, stats.size());
+ assertEquals(entry1, stats.getValues(0, null));
+ assertEquals(entry3, stats.getValues(1, null));
+ }
+
+ @Test
+ public void testApply464xlatAdjustments() {
+ final String v4Iface = "v4-wlan0";
+ final String baseIface = "wlan0";
+ final String otherIface = "other";
+ final int appUid = 10001;
+ final int rootUid = Process.ROOT_UID;
+ ArrayMap<String, String> stackedIface = new ArrayMap<>();
+ stackedIface.put(v4Iface, baseIface);
+
+ // Ipv4 traffic sent/received by an app on stacked interface.
+ final NetworkStats.Entry appEntry = new NetworkStats.Entry(
+ v4Iface, appUid, SET_DEFAULT, TAG_NONE,
+ 30501490 /* rxBytes */,
+ 22401 /* rxPackets */,
+ 876235 /* txBytes */,
+ 13805 /* txPackets */,
+ 0 /* operations */);
+
+ // Traffic measured for the root uid on the base interface.
+ final NetworkStats.Entry rootUidEntry = new NetworkStats.Entry(
+ baseIface, rootUid, SET_DEFAULT, TAG_NONE,
+ 163577 /* rxBytes */,
+ 187 /* rxPackets */,
+ 17607 /* txBytes */,
+ 97 /* txPackets */,
+ 0 /* operations */);
+
+ final NetworkStats.Entry otherEntry = new NetworkStats.Entry(
+ otherIface, appUid, SET_DEFAULT, TAG_NONE,
+ 2600 /* rxBytes */,
+ 2 /* rxPackets */,
+ 3800 /* txBytes */,
+ 3 /* txPackets */,
+ 0 /* operations */);
+
+ final NetworkStats stats = new NetworkStats(TEST_START, 3)
+ .insertEntry(appEntry)
+ .insertEntry(rootUidEntry)
+ .insertEntry(otherEntry);
+
+ stats.apply464xlatAdjustments(stackedIface);
+
+ assertEquals(3, stats.size());
+ final NetworkStats.Entry expectedAppUid = new NetworkStats.Entry(
+ v4Iface, appUid, SET_DEFAULT, TAG_NONE,
+ 30949510,
+ 22401,
+ 1152335,
+ 13805,
+ 0);
+ final NetworkStats.Entry expectedRootUid = new NetworkStats.Entry(
+ baseIface, 0, SET_DEFAULT, TAG_NONE,
+ 163577,
+ 187,
+ 17607,
+ 97,
+ 0);
+ assertEquals(expectedAppUid, stats.getValues(0, null));
+ assertEquals(expectedRootUid, stats.getValues(1, null));
+ assertEquals(otherEntry, stats.getValues(2, null));
+ }
+
+ @Test
+ public void testApply464xlatAdjustments_noStackedIface() {
+ NetworkStats.Entry firstEntry = new NetworkStats.Entry(
+ "if1", 10002, SET_DEFAULT, TAG_NONE,
+ 2600 /* rxBytes */,
+ 2 /* rxPackets */,
+ 3800 /* txBytes */,
+ 3 /* txPackets */,
+ 0 /* operations */);
+ NetworkStats.Entry secondEntry = new NetworkStats.Entry(
+ "if2", 10002, SET_DEFAULT, TAG_NONE,
+ 5000 /* rxBytes */,
+ 3 /* rxPackets */,
+ 6000 /* txBytes */,
+ 4 /* txPackets */,
+ 0 /* operations */);
+
+ NetworkStats stats = new NetworkStats(TEST_START, 2)
+ .insertEntry(firstEntry)
+ .insertEntry(secondEntry);
+
+ // Empty map: no adjustment
+ stats.apply464xlatAdjustments(new ArrayMap<>());
+
+ assertEquals(2, stats.size());
+ assertEquals(firstEntry, stats.getValues(0, null));
+ assertEquals(secondEntry, stats.getValues(1, null));
+ }
+
+ private static void assertContains(NetworkStats stats, String iface, int uid, int set,
+ int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets,
+ long txBytes, long txPackets, long operations) {
+ int index = stats.findIndex(iface, uid, set, tag, metered, roaming, defaultNetwork);
+ assertTrue(index != -1);
+ assertValues(stats, index, iface, uid, set, tag, metered, roaming, defaultNetwork,
+ rxBytes, rxPackets, txBytes, txPackets, operations);
+ }
+
+ private static void assertValues(NetworkStats stats, int index, String iface, int uid, int set,
+ int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets,
+ long txBytes, long txPackets, long operations) {
+ final NetworkStats.Entry entry = stats.getValues(index, null);
+ assertValues(entry, iface, uid, set, tag, metered, roaming, defaultNetwork);
+ assertValues(entry, rxBytes, rxPackets, txBytes, txPackets, operations);
+ }
+
+ private static void assertValues(
+ NetworkStats.Entry entry, String iface, int uid, int set, int tag, int metered,
+ int roaming, int defaultNetwork) {
+ assertEquals(iface, entry.iface);
+ assertEquals(uid, entry.uid);
+ assertEquals(set, entry.set);
+ assertEquals(tag, entry.tag);
+ assertEquals(metered, entry.metered);
+ assertEquals(roaming, entry.roaming);
+ assertEquals(defaultNetwork, entry.defaultNetwork);
+ }
+
+ private static void assertValues(NetworkStats.Entry entry, long rxBytes, long rxPackets,
+ long txBytes, long txPackets, long operations) {
+ assertEquals(rxBytes, entry.rxBytes);
+ assertEquals(rxPackets, entry.rxPackets);
+ assertEquals(txBytes, entry.txBytes);
+ assertEquals(txPackets, entry.txPackets);
+ assertEquals(operations, entry.operations);
+ }
+
+}
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
new file mode 100644
index 0000000..ab6b2f4
--- /dev/null
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -0,0 +1,351 @@
+/*
+ * 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
+
+import android.content.Context
+import android.net.ConnectivityManager.TYPE_MOBILE
+import android.net.ConnectivityManager.TYPE_WIFI
+import android.net.NetworkIdentity.SUBTYPE_COMBINED
+import android.net.NetworkIdentity.OEM_NONE
+import android.net.NetworkIdentity.OEM_PAID
+import android.net.NetworkIdentity.OEM_PRIVATE
+import android.net.NetworkIdentity.buildNetworkIdentity
+import android.net.NetworkStats.DEFAULT_NETWORK_ALL
+import android.net.NetworkStats.METERED_ALL
+import android.net.NetworkStats.ROAMING_ALL
+import android.net.NetworkTemplate.MATCH_MOBILE
+import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD
+import android.net.NetworkTemplate.MATCH_WIFI
+import android.net.NetworkTemplate.MATCH_WIFI_WILDCARD
+import android.net.NetworkTemplate.WIFI_NETWORKID_ALL
+import android.net.NetworkTemplate.NETWORK_TYPE_5G_NSA
+import android.net.NetworkTemplate.NETWORK_TYPE_ALL
+import android.net.NetworkTemplate.OEM_MANAGED_ALL
+import android.net.NetworkTemplate.OEM_MANAGED_NO
+import android.net.NetworkTemplate.OEM_MANAGED_YES
+import android.net.NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT
+import android.net.NetworkTemplate.buildTemplateWifi
+import android.net.NetworkTemplate.buildTemplateWifiWildcard
+import android.net.NetworkTemplate.buildTemplateCarrier
+import android.net.NetworkTemplate.buildTemplateMobileWithRatType
+import android.telephony.TelephonyManager
+import com.android.testutils.assertParcelSane
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertTrue
+
+private const val TEST_IMSI1 = "imsi1"
+private const val TEST_IMSI2 = "imsi2"
+private const val TEST_SSID1 = "ssid1"
+private const val TEST_SSID2 = "ssid2"
+
+@RunWith(JUnit4::class)
+class NetworkTemplateTest {
+ private val mockContext = mock(Context::class.java)
+
+ private fun buildMobileNetworkState(subscriberId: String): NetworkStateSnapshot =
+ buildNetworkState(TYPE_MOBILE, subscriberId = subscriberId)
+ private fun buildWifiNetworkState(subscriberId: String?, ssid: String?): NetworkStateSnapshot =
+ buildNetworkState(TYPE_WIFI, subscriberId = subscriberId, ssid = ssid)
+
+ private fun buildNetworkState(
+ type: Int,
+ subscriberId: String? = null,
+ ssid: String? = null,
+ oemManaged: Int = OEM_NONE
+ ): NetworkStateSnapshot {
+ val lp = LinkProperties()
+ val caps = NetworkCapabilities().apply {
+ setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false)
+ setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true)
+ setSSID(ssid)
+ setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID,
+ (oemManaged and OEM_PAID) == OEM_PAID)
+ setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE,
+ (oemManaged and OEM_PRIVATE) == OEM_PRIVATE)
+ }
+ return NetworkStateSnapshot(mock(Network::class.java), caps, lp, subscriberId, type)
+ }
+
+ private fun NetworkTemplate.assertMatches(ident: NetworkIdentity) =
+ assertTrue(matches(ident), "$this does not match $ident")
+
+ private fun NetworkTemplate.assertDoesNotMatch(ident: NetworkIdentity) =
+ assertFalse(matches(ident), "$this should match $ident")
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testWifiWildcardMatches() {
+ val templateWifiWildcard = buildTemplateWifiWildcard()
+
+ val identMobileImsi1 = buildNetworkIdentity(mockContext,
+ buildMobileNetworkState(TEST_IMSI1),
+ false, TelephonyManager.NETWORK_TYPE_UMTS)
+ val identWifiImsiNullSsid1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(null, TEST_SSID1), true, 0)
+ val identWifiImsi1Ssid1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID1), true, 0)
+
+ templateWifiWildcard.assertDoesNotMatch(identMobileImsi1)
+ templateWifiWildcard.assertMatches(identWifiImsiNullSsid1)
+ templateWifiWildcard.assertMatches(identWifiImsi1Ssid1)
+ }
+
+ @Test
+ fun testWifiMatches() {
+ val templateWifiSsid1 = buildTemplateWifi(TEST_SSID1)
+ val templateWifiSsid1ImsiNull = buildTemplateWifi(TEST_SSID1, null)
+ val templateWifiSsid1Imsi1 = buildTemplateWifi(TEST_SSID1, TEST_IMSI1)
+ val templateWifiSsidAllImsi1 = buildTemplateWifi(WIFI_NETWORKID_ALL, TEST_IMSI1)
+
+ val identMobile1 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI1),
+ false, TelephonyManager.NETWORK_TYPE_UMTS)
+ val identWifiImsiNullSsid1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(null, TEST_SSID1), true, 0)
+ val identWifiImsi1Ssid1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID1), true, 0)
+ val identWifiImsi2Ssid1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(TEST_IMSI2, TEST_SSID1), true, 0)
+ val identWifiImsi1Ssid2 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID2), true, 0)
+
+ // Verify that template with SSID only matches any subscriberId and specific SSID.
+ templateWifiSsid1.assertDoesNotMatch(identMobile1)
+ templateWifiSsid1.assertMatches(identWifiImsiNullSsid1)
+ templateWifiSsid1.assertMatches(identWifiImsi1Ssid1)
+ templateWifiSsid1.assertMatches(identWifiImsi2Ssid1)
+ templateWifiSsid1.assertDoesNotMatch(identWifiImsi1Ssid2)
+
+ // Verify that template with SSID1 and null imsi matches any network with
+ // SSID1 and null imsi.
+ templateWifiSsid1ImsiNull.assertDoesNotMatch(identMobile1)
+ templateWifiSsid1ImsiNull.assertMatches(identWifiImsiNullSsid1)
+ templateWifiSsid1ImsiNull.assertDoesNotMatch(identWifiImsi1Ssid1)
+ templateWifiSsid1ImsiNull.assertDoesNotMatch(identWifiImsi2Ssid1)
+ templateWifiSsid1ImsiNull.assertDoesNotMatch(identWifiImsi1Ssid2)
+
+ // Verify that template with SSID1 and imsi1 matches any network with
+ // SSID1 and imsi1.
+ templateWifiSsid1Imsi1.assertDoesNotMatch(identMobile1)
+ templateWifiSsid1Imsi1.assertDoesNotMatch(identWifiImsiNullSsid1)
+ templateWifiSsid1Imsi1.assertMatches(identWifiImsi1Ssid1)
+ templateWifiSsid1Imsi1.assertDoesNotMatch(identWifiImsi2Ssid1)
+ templateWifiSsid1Imsi1.assertDoesNotMatch(identWifiImsi1Ssid2)
+
+ // Verify that template with SSID all and imsi1 matches any network with
+ // any SSID and imsi1.
+ templateWifiSsidAllImsi1.assertDoesNotMatch(identMobile1)
+ templateWifiSsidAllImsi1.assertDoesNotMatch(identWifiImsiNullSsid1)
+ templateWifiSsidAllImsi1.assertMatches(identWifiImsi1Ssid1)
+ templateWifiSsidAllImsi1.assertDoesNotMatch(identWifiImsi2Ssid1)
+ templateWifiSsidAllImsi1.assertMatches(identWifiImsi1Ssid2)
+ }
+
+ @Test
+ fun testCarrierMatches() {
+ val templateCarrierImsi1 = buildTemplateCarrier(TEST_IMSI1)
+
+ val identMobile1 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI1),
+ false, TelephonyManager.NETWORK_TYPE_UMTS)
+ val identMobile2 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI2),
+ false, TelephonyManager.NETWORK_TYPE_UMTS)
+ val identWifiSsid1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(null, TEST_SSID1), true, 0)
+ val identCarrierWifiImsi1 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID1), true, 0)
+ val identCarrierWifiImsi2 = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(TEST_IMSI2, TEST_SSID1), true, 0)
+
+ templateCarrierImsi1.assertMatches(identCarrierWifiImsi1)
+ templateCarrierImsi1.assertDoesNotMatch(identCarrierWifiImsi2)
+ templateCarrierImsi1.assertDoesNotMatch(identWifiSsid1)
+ templateCarrierImsi1.assertMatches(identMobile1)
+ templateCarrierImsi1.assertDoesNotMatch(identMobile2)
+ }
+
+ @Test
+ fun testRatTypeGroupMatches() {
+ val stateMobile = buildMobileNetworkState(TEST_IMSI1)
+ // Build UMTS template that matches mobile identities with RAT in the same
+ // group with any IMSI. See {@link NetworkTemplate#getCollapsedRatType}.
+ val templateUmts = buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UMTS)
+ // Build normal template that matches mobile identities with any RAT and IMSI.
+ val templateAll = buildTemplateMobileWithRatType(null, NETWORK_TYPE_ALL)
+ // Build template with UNKNOWN RAT that matches mobile identities with RAT that
+ // cannot be determined.
+ val templateUnknown =
+ buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UNKNOWN)
+
+ val identUmts = buildNetworkIdentity(
+ mockContext, stateMobile, false, TelephonyManager.NETWORK_TYPE_UMTS)
+ val identHsdpa = buildNetworkIdentity(
+ mockContext, stateMobile, false, TelephonyManager.NETWORK_TYPE_HSDPA)
+ val identLte = buildNetworkIdentity(
+ mockContext, stateMobile, false, TelephonyManager.NETWORK_TYPE_LTE)
+ val identCombined = buildNetworkIdentity(
+ mockContext, stateMobile, false, SUBTYPE_COMBINED)
+ val identImsi2 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI2),
+ false, TelephonyManager.NETWORK_TYPE_UMTS)
+ val identWifi = buildNetworkIdentity(
+ mockContext, buildWifiNetworkState(null, TEST_SSID1), true, 0)
+
+ // Assert that identity with the same RAT matches.
+ templateUmts.assertMatches(identUmts)
+ templateAll.assertMatches(identUmts)
+ templateUnknown.assertDoesNotMatch(identUmts)
+ // Assert that identity with the RAT within the same group matches.
+ templateUmts.assertMatches(identHsdpa)
+ templateAll.assertMatches(identHsdpa)
+ templateUnknown.assertDoesNotMatch(identHsdpa)
+ // Assert that identity with the RAT out of the same group only matches template with
+ // NETWORK_TYPE_ALL.
+ templateUmts.assertDoesNotMatch(identLte)
+ templateAll.assertMatches(identLte)
+ templateUnknown.assertDoesNotMatch(identLte)
+ // Assert that identity with combined RAT only matches with template with NETWORK_TYPE_ALL
+ // and NETWORK_TYPE_UNKNOWN.
+ templateUmts.assertDoesNotMatch(identCombined)
+ templateAll.assertMatches(identCombined)
+ templateUnknown.assertMatches(identCombined)
+ // Assert that identity with different IMSI matches.
+ templateUmts.assertMatches(identImsi2)
+ templateAll.assertMatches(identImsi2)
+ templateUnknown.assertDoesNotMatch(identImsi2)
+ // Assert that wifi identity does not match.
+ templateUmts.assertDoesNotMatch(identWifi)
+ templateAll.assertDoesNotMatch(identWifi)
+ templateUnknown.assertDoesNotMatch(identWifi)
+ }
+
+ @Test
+ fun testParcelUnparcel() {
+ val templateMobile = NetworkTemplate(MATCH_MOBILE, TEST_IMSI1, null, null, METERED_ALL,
+ ROAMING_ALL, DEFAULT_NETWORK_ALL, TelephonyManager.NETWORK_TYPE_LTE,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ val templateWifi = NetworkTemplate(MATCH_WIFI, null, null, TEST_SSID1, METERED_ALL,
+ ROAMING_ALL, DEFAULT_NETWORK_ALL, 0, OEM_MANAGED_ALL,
+ SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ val templateOem = NetworkTemplate(MATCH_MOBILE, null, null, null, METERED_ALL,
+ ROAMING_ALL, DEFAULT_NETWORK_ALL, 0, OEM_MANAGED_YES,
+ SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ assertParcelSane(templateMobile, 10)
+ assertParcelSane(templateWifi, 10)
+ assertParcelSane(templateOem, 10)
+ }
+
+ // Verify NETWORK_TYPE_* constants in NetworkTemplate do not conflict with
+ // TelephonyManager#NETWORK_TYPE_* constants.
+ @Test
+ fun testNetworkTypeConstants() {
+ for (ratType in TelephonyManager.getAllNetworkTypes()) {
+ assertNotEquals(NETWORK_TYPE_ALL, ratType)
+ assertNotEquals(NETWORK_TYPE_5G_NSA, ratType)
+ }
+ }
+
+ @Test
+ fun testOemNetworkConstants() {
+ val constantValues = arrayOf(OEM_MANAGED_YES, OEM_MANAGED_ALL, OEM_MANAGED_NO,
+ OEM_PAID, OEM_PRIVATE, OEM_PAID or OEM_PRIVATE)
+
+ // Verify that "not OEM managed network" constants are equal.
+ assertEquals(OEM_MANAGED_NO, OEM_NONE)
+
+ // Verify the constants don't conflict.
+ assertEquals(constantValues.size, constantValues.distinct().count())
+ }
+
+ /**
+ * Helper to enumerate and assert OEM managed wifi and mobile {@code NetworkTemplate}s match
+ * their the appropriate OEM managed {@code NetworkIdentity}s.
+ *
+ * @param networkType {@code TYPE_MOBILE} or {@code TYPE_WIFI}
+ * @param matchType A match rule from {@code NetworkTemplate.MATCH_*} corresponding to the
+ * networkType.
+ * @param subscriberId To be populated with {@code TEST_IMSI*} only if networkType is
+ * {@code TYPE_MOBILE}. May be left as null when matchType is
+ * {@link NetworkTemplate.MATCH_MOBILE_WILDCARD}.
+ * @param templateSsid Top be populated with {@code TEST_SSID*} only if networkType is
+ * {@code TYPE_WIFI}. May be left as null when matchType is
+ * {@link NetworkTemplate.MATCH_WIFI_WILDCARD}.
+ * @param identSsid If networkType is {@code TYPE_WIFI}, this value must *NOT* be null. Provide
+ * one of {@code TEST_SSID*}.
+ */
+ private fun matchOemManagedIdent(
+ networkType: Int,
+ matchType: Int,
+ subscriberId: String? = null,
+ templateSsid: String? = null,
+ identSsid: String? = null
+ ) {
+ val oemManagedStates = arrayOf(OEM_NONE, OEM_PAID, OEM_PRIVATE, OEM_PAID or OEM_PRIVATE)
+ val matchSubscriberIds = arrayOf(subscriberId)
+
+ val templateOemYes = NetworkTemplate(matchType, subscriberId, matchSubscriberIds,
+ templateSsid, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_YES, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ val templateOemAll = NetworkTemplate(matchType, subscriberId, matchSubscriberIds,
+ templateSsid, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+
+ for (identityOemManagedState in oemManagedStates) {
+ val ident = buildNetworkIdentity(mockContext, buildNetworkState(networkType,
+ subscriberId, identSsid, identityOemManagedState), /*defaultNetwork=*/false,
+ /*subType=*/0)
+
+ // Create a template with each OEM managed type and match it against the NetworkIdentity
+ for (templateOemManagedState in oemManagedStates) {
+ val template = NetworkTemplate(matchType, subscriberId, matchSubscriberIds,
+ templateSsid, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL,
+ NETWORK_TYPE_ALL, templateOemManagedState, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ if (identityOemManagedState == templateOemManagedState) {
+ template.assertMatches(ident)
+ } else {
+ template.assertDoesNotMatch(ident)
+ }
+ }
+ // OEM_MANAGED_ALL ignores OEM state.
+ templateOemAll.assertMatches(ident)
+ if (identityOemManagedState == OEM_NONE) {
+ // OEM_MANAGED_YES matches everything except OEM_NONE.
+ templateOemYes.assertDoesNotMatch(ident)
+ } else {
+ templateOemYes.assertMatches(ident)
+ }
+ }
+ }
+
+ @Test
+ fun testOemManagedMatchesIdent() {
+ matchOemManagedIdent(TYPE_MOBILE, MATCH_MOBILE, subscriberId = TEST_IMSI1)
+ matchOemManagedIdent(TYPE_MOBILE, MATCH_MOBILE_WILDCARD)
+ matchOemManagedIdent(TYPE_WIFI, MATCH_WIFI, templateSsid = TEST_SSID1,
+ identSsid = TEST_SSID1)
+ matchOemManagedIdent(TYPE_WIFI, MATCH_WIFI_WILDCARD, identSsid = TEST_SSID1)
+ }
+}
diff --git a/tests/unit/java/android/net/NetworkUtilsTest.java b/tests/unit/java/android/net/NetworkUtilsTest.java
new file mode 100644
index 0000000..7748288
--- /dev/null
+++ b/tests/unit/java/android/net/NetworkUtilsTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static junit.framework.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigInteger;
+import java.util.TreeSet;
+
+@RunWith(AndroidJUnit4.class)
+@androidx.test.filters.SmallTest
+public class NetworkUtilsTest {
+ @Test
+ public void testRoutedIPv4AddressCount() {
+ final TreeSet<IpPrefix> set = new TreeSet<>(IpPrefix.lengthComparator());
+ // No routes routes to no addresses.
+ assertEquals(0, NetworkUtils.routedIPv4AddressCount(set));
+
+ set.add(new IpPrefix("0.0.0.0/0"));
+ assertEquals(1l << 32, NetworkUtils.routedIPv4AddressCount(set));
+
+ set.add(new IpPrefix("20.18.0.0/16"));
+ set.add(new IpPrefix("20.18.0.0/24"));
+ set.add(new IpPrefix("20.18.0.0/8"));
+ // There is a default route, still covers everything
+ assertEquals(1l << 32, NetworkUtils.routedIPv4AddressCount(set));
+
+ set.clear();
+ set.add(new IpPrefix("20.18.0.0/24"));
+ set.add(new IpPrefix("20.18.0.0/8"));
+ // The 8-length includes the 24-length prefix
+ assertEquals(1l << 24, NetworkUtils.routedIPv4AddressCount(set));
+
+ set.add(new IpPrefix("10.10.10.126/25"));
+ // The 8-length does not include this 25-length prefix
+ assertEquals((1l << 24) + (1 << 7), NetworkUtils.routedIPv4AddressCount(set));
+
+ set.clear();
+ set.add(new IpPrefix("1.2.3.4/32"));
+ set.add(new IpPrefix("1.2.3.4/32"));
+ set.add(new IpPrefix("1.2.3.4/32"));
+ set.add(new IpPrefix("1.2.3.4/32"));
+ assertEquals(1l, NetworkUtils.routedIPv4AddressCount(set));
+
+ set.add(new IpPrefix("1.2.3.5/32"));
+ set.add(new IpPrefix("1.2.3.6/32"));
+
+ set.add(new IpPrefix("1.2.3.7/32"));
+ set.add(new IpPrefix("1.2.3.8/32"));
+ set.add(new IpPrefix("1.2.3.9/32"));
+ set.add(new IpPrefix("1.2.3.0/32"));
+ assertEquals(7l, NetworkUtils.routedIPv4AddressCount(set));
+
+ // 1.2.3.4/30 eats 1.2.3.{4-7}/32
+ set.add(new IpPrefix("1.2.3.4/30"));
+ set.add(new IpPrefix("6.2.3.4/28"));
+ set.add(new IpPrefix("120.2.3.4/16"));
+ assertEquals(7l - 4 + 4 + 16 + 65536, NetworkUtils.routedIPv4AddressCount(set));
+ }
+
+ @Test
+ public void testRoutedIPv6AddressCount() {
+ final TreeSet<IpPrefix> set = new TreeSet<>(IpPrefix.lengthComparator());
+ // No routes routes to no addresses.
+ assertEquals(BigInteger.ZERO, NetworkUtils.routedIPv6AddressCount(set));
+
+ set.add(new IpPrefix("::/0"));
+ assertEquals(BigInteger.ONE.shiftLeft(128), NetworkUtils.routedIPv6AddressCount(set));
+
+ set.add(new IpPrefix("1234:622a::18/64"));
+ set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/96"));
+ set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/8"));
+ // There is a default route, still covers everything
+ assertEquals(BigInteger.ONE.shiftLeft(128), NetworkUtils.routedIPv6AddressCount(set));
+
+ set.clear();
+ set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/96"));
+ set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/8"));
+ // The 8-length includes the 96-length prefix
+ assertEquals(BigInteger.ONE.shiftLeft(120), NetworkUtils.routedIPv6AddressCount(set));
+
+ set.add(new IpPrefix("10::26/64"));
+ // The 8-length does not include this 64-length prefix
+ assertEquals(BigInteger.ONE.shiftLeft(120).add(BigInteger.ONE.shiftLeft(64)),
+ NetworkUtils.routedIPv6AddressCount(set));
+
+ set.clear();
+ set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128"));
+ set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128"));
+ set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128"));
+ set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128"));
+ assertEquals(BigInteger.ONE, NetworkUtils.routedIPv6AddressCount(set));
+
+ set.add(new IpPrefix("add4:f00:80:f7:1111::6ad5/128"));
+ set.add(new IpPrefix("add4:f00:80:f7:1111::6ad6/128"));
+ set.add(new IpPrefix("add4:f00:80:f7:1111::6ad7/128"));
+ set.add(new IpPrefix("add4:f00:80:f7:1111::6ad8/128"));
+ set.add(new IpPrefix("add4:f00:80:f7:1111::6ad9/128"));
+ set.add(new IpPrefix("add4:f00:80:f7:1111::6ad0/128"));
+ assertEquals(BigInteger.valueOf(7), NetworkUtils.routedIPv6AddressCount(set));
+
+ // add4:f00:80:f7:1111::6ad4/126 eats add4:f00:8[:f7:1111::6ad{4-7}/128
+ set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/126"));
+ set.add(new IpPrefix("d00d:f00:80:f7:1111::6ade/124"));
+ set.add(new IpPrefix("f00b:a33::/112"));
+ assertEquals(BigInteger.valueOf(7l - 4 + 4 + 16 + 65536),
+ NetworkUtils.routedIPv6AddressCount(set));
+ }
+}
diff --git a/tests/unit/java/android/net/QosSocketFilterTest.java b/tests/unit/java/android/net/QosSocketFilterTest.java
new file mode 100644
index 0000000..ad58960
--- /dev/null
+++ b/tests/unit/java/android/net/QosSocketFilterTest.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;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+
+@RunWith(AndroidJUnit4.class)
+@androidx.test.filters.SmallTest
+public class QosSocketFilterTest {
+
+ @Test
+ public void testPortExactMatch() {
+ final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4");
+ final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4");
+ assertTrue(QosSocketFilter.matchesLocalAddress(
+ new InetSocketAddress(addressA, 10), addressB, 10, 10));
+
+ }
+
+ @Test
+ public void testPortLessThanStart() {
+ final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4");
+ final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4");
+ assertFalse(QosSocketFilter.matchesLocalAddress(
+ new InetSocketAddress(addressA, 8), addressB, 10, 10));
+ }
+
+ @Test
+ public void testPortGreaterThanEnd() {
+ final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4");
+ final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4");
+ assertFalse(QosSocketFilter.matchesLocalAddress(
+ new InetSocketAddress(addressA, 18), addressB, 10, 10));
+ }
+
+ @Test
+ public void testPortBetweenStartAndEnd() {
+ final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4");
+ final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4");
+ assertTrue(QosSocketFilter.matchesLocalAddress(
+ new InetSocketAddress(addressA, 10), addressB, 8, 18));
+ }
+
+ @Test
+ public void testAddressesDontMatch() {
+ final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4");
+ final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.5");
+ assertFalse(QosSocketFilter.matchesLocalAddress(
+ new InetSocketAddress(addressA, 10), addressB, 10, 10));
+ }
+}
+
diff --git a/tests/unit/java/android/net/TelephonyNetworkSpecifierTest.java b/tests/unit/java/android/net/TelephonyNetworkSpecifierTest.java
new file mode 100644
index 0000000..6714bb1
--- /dev/null
+++ b/tests/unit/java/android/net/TelephonyNetworkSpecifierTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.wifi.WifiNetworkSpecifier;
+import android.telephony.SubscriptionManager;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit test for {@link android.net.TelephonyNetworkSpecifier}.
+ */
+@SmallTest
+public class TelephonyNetworkSpecifierTest {
+ private static final int TEST_SUBID = 5;
+ private static final String TEST_SSID = "Test123";
+
+ /**
+ * Validate that IllegalArgumentException will be thrown if build TelephonyNetworkSpecifier
+ * without calling {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}.
+ */
+ @Test
+ public void testBuilderBuildWithDefault() {
+ try {
+ new TelephonyNetworkSpecifier.Builder().build();
+ } catch (IllegalArgumentException iae) {
+ // expected, test pass
+ }
+ }
+
+ /**
+ * Validate that no exception will be thrown even if pass invalid subscription id to
+ * {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}.
+ */
+ @Test
+ public void testBuilderBuildWithInvalidSubId() {
+ TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ .build();
+ assertEquals(specifier.getSubscriptionId(), SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+
+ /**
+ * Validate the correctness of TelephonyNetworkSpecifier when provide valid subId.
+ */
+ @Test
+ public void testBuilderBuildWithValidSubId() {
+ final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(TEST_SUBID)
+ .build();
+ assertEquals(TEST_SUBID, specifier.getSubscriptionId());
+ }
+
+ /**
+ * Validate that parcel marshalling/unmarshalling works.
+ */
+ @Test
+ public void testParcel() {
+ TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(TEST_SUBID)
+ .build();
+ assertParcelSane(specifier, 1 /* fieldCount */);
+ }
+
+ /**
+ * Validate the behavior of method canBeSatisfiedBy().
+ */
+ @Test
+ public void testCanBeSatisfiedBy() {
+ final TelephonyNetworkSpecifier tns1 = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(TEST_SUBID)
+ .build();
+ final TelephonyNetworkSpecifier tns2 = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(TEST_SUBID)
+ .build();
+ final WifiNetworkSpecifier wns = new WifiNetworkSpecifier.Builder()
+ .setSsid(TEST_SSID)
+ .build();
+ final MatchAllNetworkSpecifier mans = new MatchAllNetworkSpecifier();
+
+ // Test equality
+ assertEquals(tns1, tns2);
+ assertTrue(tns1.canBeSatisfiedBy(tns1));
+ assertTrue(tns1.canBeSatisfiedBy(tns2));
+
+ // Test other edge cases.
+ assertFalse(tns1.canBeSatisfiedBy(null));
+ assertFalse(tns1.canBeSatisfiedBy(wns));
+ assertTrue(tns1.canBeSatisfiedBy(mans));
+ }
+}
diff --git a/tests/unit/java/android/net/VpnManagerTest.java b/tests/unit/java/android/net/VpnManagerTest.java
new file mode 100644
index 0000000..3135062
--- /dev/null
+++ b/tests/unit/java/android/net/VpnManagerTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.test.mock.MockContext;
+import android.util.SparseArray;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.MessageUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link VpnManager}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VpnManagerTest {
+ private static final String PKG_NAME = "fooPackage";
+
+ private static final String SESSION_NAME_STRING = "testSession";
+ private static final String SERVER_ADDR_STRING = "1.2.3.4";
+ private static final String IDENTITY_STRING = "Identity";
+ private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
+
+ private IVpnManager mMockService;
+ private VpnManager mVpnManager;
+ private final MockContext mMockContext =
+ new MockContext() {
+ @Override
+ public String getOpPackageName() {
+ return PKG_NAME;
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ mMockService = mock(IVpnManager.class);
+ mVpnManager = new VpnManager(mMockContext, mMockService);
+ }
+
+ @Test
+ public void testProvisionVpnProfilePreconsented() throws Exception {
+ final PlatformVpnProfile profile = getPlatformVpnProfile();
+ when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+ .thenReturn(true);
+
+ // Expect there to be no intent returned, as consent has already been granted.
+ assertNull(mVpnManager.provisionVpnProfile(profile));
+ verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+ }
+
+ @Test
+ public void testProvisionVpnProfileNeedsConsent() throws Exception {
+ final PlatformVpnProfile profile = getPlatformVpnProfile();
+ when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+ .thenReturn(false);
+
+ // Expect intent to be returned, as consent has not already been granted.
+ final Intent intent = mVpnManager.provisionVpnProfile(profile);
+ assertNotNull(intent);
+
+ final ComponentName expectedComponentName =
+ ComponentName.unflattenFromString(
+ "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog");
+ assertEquals(expectedComponentName, intent.getComponent());
+ verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+ }
+
+ @Test
+ public void testDeleteProvisionedVpnProfile() throws Exception {
+ mVpnManager.deleteProvisionedVpnProfile();
+ verify(mMockService).deleteVpnProfile(eq(PKG_NAME));
+ }
+
+ @Test
+ public void testStartProvisionedVpnProfile() throws Exception {
+ mVpnManager.startProvisionedVpnProfile();
+ verify(mMockService).startVpnProfile(eq(PKG_NAME));
+ }
+
+ @Test
+ public void testStopProvisionedVpnProfile() throws Exception {
+ mVpnManager.stopProvisionedVpnProfile();
+ verify(mMockService).stopVpnProfile(eq(PKG_NAME));
+ }
+
+ private Ikev2VpnProfile getPlatformVpnProfile() throws Exception {
+ return new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
+ .setBypassable(true)
+ .setMaxMtu(1300)
+ .setMetered(true)
+ .setAuthPsk(PSK_BYTES)
+ .build();
+ }
+
+ @Test
+ public void testVpnTypesEqual() throws Exception {
+ SparseArray<String> vmVpnTypes = MessageUtils.findMessageNames(
+ new Class[] { VpnManager.class }, new String[]{ "TYPE_VPN_" });
+ SparseArray<String> nativeVpnType = MessageUtils.findMessageNames(
+ new Class[] { NativeVpnType.class }, new String[]{ "" });
+
+ // TYPE_VPN_NONE = -1 is only defined in VpnManager.
+ assertEquals(vmVpnTypes.size() - 1, nativeVpnType.size());
+ for (int i = VpnManager.TYPE_VPN_SERVICE; i < vmVpnTypes.size(); i++) {
+ assertEquals(vmVpnTypes.get(i), "TYPE_VPN_" + nativeVpnType.get(i));
+ }
+ }
+}
diff --git a/tests/unit/java/android/net/VpnTransportInfoTest.java b/tests/unit/java/android/net/VpnTransportInfoTest.java
new file mode 100644
index 0000000..ccaa5cf
--- /dev/null
+++ b/tests/unit/java/android/net/VpnTransportInfoTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
+import static android.net.NetworkCapabilities.REDACT_NONE;
+
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VpnTransportInfoTest {
+
+ @Test
+ public void testParceling() {
+ VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, "12345");
+ assertParcelSane(v, 2 /* fieldCount */);
+ }
+
+ @Test
+ public void testEqualsAndHashCode() {
+ String session1 = "12345";
+ String session2 = "6789";
+ VpnTransportInfo v11 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, session1);
+ VpnTransportInfo v12 = new VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE, session1);
+ VpnTransportInfo v13 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, session1);
+ VpnTransportInfo v14 = new VpnTransportInfo(VpnManager.TYPE_VPN_LEGACY, session1);
+ VpnTransportInfo v15 = new VpnTransportInfo(VpnManager.TYPE_VPN_OEM, session1);
+ VpnTransportInfo v21 = new VpnTransportInfo(VpnManager.TYPE_VPN_LEGACY, session2);
+
+ VpnTransportInfo v31 = v11.makeCopy(REDACT_FOR_NETWORK_SETTINGS);
+ VpnTransportInfo v32 = v13.makeCopy(REDACT_FOR_NETWORK_SETTINGS);
+
+ assertNotEquals(v11, v12);
+ assertNotEquals(v13, v14);
+ assertNotEquals(v14, v15);
+ assertNotEquals(v14, v21);
+
+ assertEquals(v11, v13);
+ assertEquals(v31, v32);
+ assertEquals(v11.hashCode(), v13.hashCode());
+ assertEquals(REDACT_FOR_NETWORK_SETTINGS, v32.getApplicableRedactions());
+ assertEquals(session1, v15.makeCopy(REDACT_NONE).getSessionId());
+ }
+}
diff --git a/tests/unit/java/android/net/ipmemorystore/ParcelableTests.java b/tests/unit/java/android/net/ipmemorystore/ParcelableTests.java
new file mode 100644
index 0000000..603c875
--- /dev/null
+++ b/tests/unit/java/android/net/ipmemorystore/ParcelableTests.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2018 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.ipmemorystore;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.networkstack.aidl.quirks.IPv6ProvisioningLossQuirk;
+import android.net.networkstack.aidl.quirks.IPv6ProvisioningLossQuirkParcelable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Modifier;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.Collections;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ParcelableTests {
+ @Test
+ public void testNetworkAttributesParceling() throws Exception {
+ final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
+ NetworkAttributes in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+ // lease will expire in two hours
+ builder.setAssignedV4AddressExpiry(System.currentTimeMillis() + 7_200_000);
+ // cluster stays null this time around
+ builder.setDnsAddresses(Collections.emptyList());
+ builder.setMtu(18);
+ in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("6.7.8.9"));
+ builder.setAssignedV4AddressExpiry(System.currentTimeMillis() + 3_600_000);
+ builder.setCluster("groupHint");
+ builder.setDnsAddresses(Arrays.asList(
+ InetAddress.getByName("ACA1:652B:0911:DE8F:1200:115E:913B:AA2A"),
+ InetAddress.getByName("6.7.8.9")));
+ builder.setMtu(1_000_000);
+ in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ builder.setMtu(null);
+ in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ // Verify that this test does not miss any new field added later.
+ // If any field is added to NetworkAttributes it must be tested here for parceling
+ // roundtrip.
+ assertEquals(6, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
+ .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
+ }
+
+ @Test
+ public void testPrivateDataParceling() throws Exception {
+ final Blob in = new Blob();
+ in.data = new byte[] {89, 111, 108, 111};
+ final Blob out = parcelingRoundTrip(in);
+ // Object.equals on byte[] tests the references
+ assertEquals(in.data.length, out.data.length);
+ assertTrue(Arrays.equals(in.data, out.data));
+ }
+
+ @Test
+ public void testSameL3NetworkResponseParceling() throws Exception {
+ final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable();
+ parcelable.l2Key1 = "key 1";
+ parcelable.l2Key2 = "key 2";
+ parcelable.confidence = 0.43f;
+
+ final SameL3NetworkResponse in = new SameL3NetworkResponse(parcelable);
+ assertEquals("key 1", in.l2Key1);
+ assertEquals("key 2", in.l2Key2);
+ assertEquals(0.43f, in.confidence, 0.01f /* delta */);
+
+ final SameL3NetworkResponse out =
+ new SameL3NetworkResponse(parcelingRoundTrip(in.toParcelable()));
+
+ assertEquals(in, out);
+ assertEquals(in.l2Key1, out.l2Key1);
+ assertEquals(in.l2Key2, out.l2Key2);
+ assertEquals(in.confidence, out.confidence, 0.01f /* delta */);
+ }
+
+ @Test
+ public void testIPv6ProvisioningLossQuirkParceling() throws Exception {
+ final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
+ final IPv6ProvisioningLossQuirkParcelable parcelable =
+ new IPv6ProvisioningLossQuirkParcelable();
+ final long expiry = System.currentTimeMillis() + 7_200_000;
+
+ parcelable.detectionCount = 3;
+ parcelable.quirkExpiry = expiry; // quirk info will expire in two hours
+ builder.setIpv6ProvLossQuirk(IPv6ProvisioningLossQuirk.fromStableParcelable(parcelable));
+ final NetworkAttributes in = builder.build();
+
+ final NetworkAttributes out = new NetworkAttributes(parcelingRoundTrip(in.toParcelable()));
+ assertEquals(out.ipv6ProvisioningLossQuirk, in.ipv6ProvisioningLossQuirk);
+ }
+
+ private <T extends Parcelable> T parcelingRoundTrip(final T in) throws Exception {
+ final Parcel p = Parcel.obtain();
+ in.writeToParcel(p, /* flags */ 0);
+ p.setDataPosition(0);
+ final byte[] marshalledData = p.marshall();
+ p.recycle();
+
+ final Parcel q = Parcel.obtain();
+ q.unmarshall(marshalledData, 0, marshalledData.length);
+ q.setDataPosition(0);
+
+ final Parcelable.Creator<T> creator = (Parcelable.Creator<T>)
+ in.getClass().getField("CREATOR").get(null); // static object, so null receiver
+ final T unmarshalled = (T) creator.createFromParcel(q);
+ q.recycle();
+ return unmarshalled;
+ }
+}
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
new file mode 100644
index 0000000..b0a9b8a
--- /dev/null
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2017 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.nsd;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.testutils.HandlerUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NsdManagerTest {
+
+ static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
+
+ @Mock Context mContext;
+ @Mock INsdManager mService;
+ MockServiceHandler mServiceHandler;
+
+ NsdManager mManager;
+
+ long mTimeoutMs = 200; // non-final so that tests can adjust the value.
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mServiceHandler = spy(MockServiceHandler.create(mContext));
+ when(mService.getMessenger()).thenReturn(new Messenger(mServiceHandler));
+
+ mManager = makeManager();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ HandlerUtils.waitForIdle(mServiceHandler, mTimeoutMs);
+ mServiceHandler.chan.disconnect();
+ mServiceHandler.stop();
+ if (mManager != null) {
+ mManager.disconnect();
+ }
+ }
+
+ @Test
+ public void testResolveService() {
+ NsdManager manager = mManager;
+
+ NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
+ NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type");
+ NsdManager.ResolveListener listener = mock(NsdManager.ResolveListener.class);
+
+ manager.resolveService(request, listener);
+ int key1 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+ int err = 33;
+ sendResponse(NsdManager.RESOLVE_SERVICE_FAILED, err, key1, null);
+ verify(listener, timeout(mTimeoutMs).times(1)).onResolveFailed(request, err);
+
+ manager.resolveService(request, listener);
+ int key2 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+ sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key2, reply);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
+ }
+
+ @Test
+ public void testParallelResolveService() {
+ NsdManager manager = mManager;
+
+ NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
+ NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type");
+
+ NsdManager.ResolveListener listener1 = mock(NsdManager.ResolveListener.class);
+ NsdManager.ResolveListener listener2 = mock(NsdManager.ResolveListener.class);
+
+ manager.resolveService(request, listener1);
+ int key1 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+
+ manager.resolveService(request, listener2);
+ int key2 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+
+ sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key2, reply);
+ sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key1, reply);
+
+ verify(listener1, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
+ verify(listener2, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
+ }
+
+ @Test
+ public void testRegisterService() {
+ NsdManager manager = mManager;
+
+ NsdServiceInfo request1 = new NsdServiceInfo("a_name", "a_type");
+ NsdServiceInfo request2 = new NsdServiceInfo("another_name", "another_type");
+ request1.setPort(2201);
+ request2.setPort(2202);
+ NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+ NsdManager.RegistrationListener listener2 = mock(NsdManager.RegistrationListener.class);
+
+ // Register two services
+ manager.registerService(request1, PROTOCOL, listener1);
+ int key1 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+ manager.registerService(request2, PROTOCOL, listener2);
+ int key2 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+ // First reques fails, second request succeeds
+ sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key2, request2);
+ verify(listener2, timeout(mTimeoutMs).times(1)).onServiceRegistered(request2);
+
+ int err = 1;
+ sendResponse(NsdManager.REGISTER_SERVICE_FAILED, err, key1, request1);
+ verify(listener1, timeout(mTimeoutMs).times(1)).onRegistrationFailed(request1, err);
+
+ // Client retries first request, it succeeds
+ manager.registerService(request1, PROTOCOL, listener1);
+ int key3 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+ sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key3, request1);
+ verify(listener1, timeout(mTimeoutMs).times(1)).onServiceRegistered(request1);
+
+ // First request is unregistered, it succeeds
+ manager.unregisterService(listener1);
+ int key3again = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+ assertEquals(key3, key3again);
+
+ sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key3again, null);
+ verify(listener1, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request1);
+
+ // Second request is unregistered, it fails
+ manager.unregisterService(listener2);
+ int key2again = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+ assertEquals(key2, key2again);
+
+ sendResponse(NsdManager.UNREGISTER_SERVICE_FAILED, err, key2again, null);
+ verify(listener2, timeout(mTimeoutMs).times(1)).onUnregistrationFailed(request2, err);
+
+ // TODO: do not unregister listener until service is unregistered
+ // Client retries unregistration of second request, it succeeds
+ //manager.unregisterService(listener2);
+ //int key2yetAgain = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+ //assertEquals(key2, key2yetAgain);
+
+ //sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key2yetAgain, null);
+ //verify(listener2, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request2);
+ }
+
+ @Test
+ public void testDiscoverService() {
+ NsdManager manager = mManager;
+
+ NsdServiceInfo reply1 = new NsdServiceInfo("a_name", "a_type");
+ NsdServiceInfo reply2 = new NsdServiceInfo("another_name", "a_type");
+ NsdServiceInfo reply3 = new NsdServiceInfo("a_third_name", "a_type");
+
+ NsdManager.DiscoveryListener listener = mock(NsdManager.DiscoveryListener.class);
+
+ // Client registers for discovery, request fails
+ manager.discoverServices("a_type", PROTOCOL, listener);
+ int key1 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+ int err = 1;
+ sendResponse(NsdManager.DISCOVER_SERVICES_FAILED, err, key1, null);
+ verify(listener, timeout(mTimeoutMs).times(1)).onStartDiscoveryFailed("a_type", err);
+
+ // Client retries, request succeeds
+ manager.discoverServices("a_type", PROTOCOL, listener);
+ int key2 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+ sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key2, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
+
+
+ // mdns notifies about services
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply1);
+
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply2);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply2);
+
+ sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply2);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply2);
+
+
+ // Client unregisters its listener
+ manager.stopServiceDiscovery(listener);
+ int key2again = verifyRequest(NsdManager.STOP_DISCOVERY);
+ assertEquals(key2, key2again);
+
+ // TODO: unregister listener immediately and stop notifying it about services
+ // Notifications are still passed to the client's listener
+ sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply1);
+
+ // Client is notified of complete unregistration
+ sendResponse(NsdManager.STOP_DISCOVERY_SUCCEEDED, 0, key2again, "a_type");
+ verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStopped("a_type");
+
+ // Notifications are not passed to the client anymore
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply3);
+ verify(listener, timeout(mTimeoutMs).times(0)).onServiceLost(reply3);
+
+
+ // Client registers for service discovery
+ reset(listener);
+ manager.discoverServices("a_type", PROTOCOL, listener);
+ int key3 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+ sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key3, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
+
+ // Client unregisters immediately, it fails
+ manager.stopServiceDiscovery(listener);
+ int key3again = verifyRequest(NsdManager.STOP_DISCOVERY);
+ assertEquals(key3, key3again);
+
+ err = 2;
+ sendResponse(NsdManager.STOP_DISCOVERY_FAILED, err, key3again, "a_type");
+ verify(listener, timeout(mTimeoutMs).times(1)).onStopDiscoveryFailed("a_type", err);
+
+ // New notifications are not passed to the client anymore
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key3, reply1);
+ verify(listener, timeout(mTimeoutMs).times(0)).onServiceFound(reply1);
+ }
+
+ @Test
+ public void testInvalidCalls() {
+ NsdManager manager = mManager;
+
+ NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+ NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
+ NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
+
+ NsdServiceInfo invalidService = new NsdServiceInfo(null, null);
+ NsdServiceInfo validService = new NsdServiceInfo("a_name", "a_type");
+ validService.setPort(2222);
+
+ // Service registration
+ // - invalid arguments
+ mustFail(() -> { manager.unregisterService(null); });
+ mustFail(() -> { manager.registerService(null, -1, null); });
+ mustFail(() -> { manager.registerService(null, PROTOCOL, listener1); });
+ mustFail(() -> { manager.registerService(invalidService, PROTOCOL, listener1); });
+ mustFail(() -> { manager.registerService(validService, -1, listener1); });
+ mustFail(() -> { manager.registerService(validService, PROTOCOL, null); });
+ manager.registerService(validService, PROTOCOL, listener1);
+ // - listener already registered
+ mustFail(() -> { manager.registerService(validService, PROTOCOL, listener1); });
+ manager.unregisterService(listener1);
+ // TODO: make listener immediately reusable
+ //mustFail(() -> { manager.unregisterService(listener1); });
+ //manager.registerService(validService, PROTOCOL, listener1);
+
+ // Discover service
+ // - invalid arguments
+ mustFail(() -> { manager.stopServiceDiscovery(null); });
+ mustFail(() -> { manager.discoverServices(null, -1, null); });
+ mustFail(() -> { manager.discoverServices(null, PROTOCOL, listener2); });
+ mustFail(() -> { manager.discoverServices("a_service", -1, listener2); });
+ mustFail(() -> { manager.discoverServices("a_service", PROTOCOL, null); });
+ manager.discoverServices("a_service", PROTOCOL, listener2);
+ // - listener already registered
+ mustFail(() -> { manager.discoverServices("another_service", PROTOCOL, listener2); });
+ manager.stopServiceDiscovery(listener2);
+ // TODO: make listener immediately reusable
+ //mustFail(() -> { manager.stopServiceDiscovery(listener2); });
+ //manager.discoverServices("another_service", PROTOCOL, listener2);
+
+ // Resolver service
+ // - invalid arguments
+ mustFail(() -> { manager.resolveService(null, null); });
+ mustFail(() -> { manager.resolveService(null, listener3); });
+ mustFail(() -> { manager.resolveService(invalidService, listener3); });
+ mustFail(() -> { manager.resolveService(validService, null); });
+ manager.resolveService(validService, listener3);
+ // - listener already registered:w
+ mustFail(() -> { manager.resolveService(validService, listener3); });
+ }
+
+ public void mustFail(Runnable fn) {
+ try {
+ fn.run();
+ fail();
+ } catch (Exception expected) {
+ }
+ }
+
+ NsdManager makeManager() {
+ NsdManager manager = new NsdManager(mContext, mService);
+ // Acknowledge first two messages connecting the AsyncChannel.
+ verify(mServiceHandler, timeout(mTimeoutMs).times(2)).handleMessage(any());
+ reset(mServiceHandler);
+ assertNotNull(mServiceHandler.chan);
+ return manager;
+ }
+
+ int verifyRequest(int expectedMessageType) {
+ HandlerUtils.waitForIdle(mServiceHandler, mTimeoutMs);
+ verify(mServiceHandler, timeout(mTimeoutMs)).handleMessage(any());
+ reset(mServiceHandler);
+ Message received = mServiceHandler.getLastMessage();
+ assertEquals(NsdManager.nameOf(expectedMessageType), NsdManager.nameOf(received.what));
+ return received.arg2;
+ }
+
+ void sendResponse(int replyType, int arg, int key, Object obj) {
+ mServiceHandler.chan.sendMessage(replyType, arg, key, obj);
+ }
+
+ // Implements the server side of AsyncChannel connection protocol
+ public static class MockServiceHandler extends Handler {
+ public final Context context;
+ public AsyncChannel chan;
+ public Message lastMessage;
+
+ MockServiceHandler(Looper l, Context c) {
+ super(l);
+ context = c;
+ }
+
+ synchronized Message getLastMessage() {
+ return lastMessage;
+ }
+
+ synchronized void setLastMessage(Message msg) {
+ lastMessage = obtainMessage();
+ lastMessage.copyFrom(msg);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ setLastMessage(msg);
+ if (msg.what == AsyncChannel.CMD_CHANNEL_FULL_CONNECTION) {
+ chan = new AsyncChannel();
+ chan.connect(context, this, msg.replyTo);
+ chan.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
+ }
+ }
+
+ void stop() {
+ getLooper().quitSafely();
+ }
+
+ static MockServiceHandler create(Context context) {
+ HandlerThread t = new HandlerThread("mock-service-handler");
+ t.start();
+ return new MockServiceHandler(t.getLooper(), context);
+ }
+ }
+}
diff --git a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
new file mode 100644
index 0000000..94dfc75
--- /dev/null
+++ b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2014 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.nsd;
+
+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.os.Bundle;
+import android.os.Parcel;
+import android.os.StrictMode;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Map;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NsdServiceInfoTest {
+
+ public final static InetAddress LOCALHOST;
+ static {
+ // Because test.
+ StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
+ StrictMode.setThreadPolicy(policy);
+
+ InetAddress _host = null;
+ try {
+ _host = InetAddress.getLocalHost();
+ } catch (UnknownHostException e) { }
+ LOCALHOST = _host;
+ }
+
+ @Test
+ public void testLimits() throws Exception {
+ NsdServiceInfo info = new NsdServiceInfo();
+
+ // Non-ASCII keys.
+ boolean exceptionThrown = false;
+ try {
+ info.setAttribute("猫", "meow");
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertEmptyServiceInfo(info);
+
+ // ASCII keys with '=' character.
+ exceptionThrown = false;
+ try {
+ info.setAttribute("kitten=", "meow");
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertEmptyServiceInfo(info);
+
+ // Single key + value length too long.
+ exceptionThrown = false;
+ try {
+ String longValue = "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" +
+ "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" +
+ "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" +
+ "ooooooooooooooooooooooooooooong"; // 248 characters.
+ info.setAttribute("longcat", longValue); // Key + value == 255 characters.
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertEmptyServiceInfo(info);
+
+ // Total TXT record length too long.
+ exceptionThrown = false;
+ int recordsAdded = 0;
+ try {
+ for (int i = 100; i < 300; ++i) {
+ // 6 char key + 5 char value + 2 bytes overhead = 13 byte record length.
+ String key = String.format("key%d", i);
+ info.setAttribute(key, "12345");
+ recordsAdded++;
+ }
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertTrue(100 == recordsAdded);
+ assertTrue(info.getTxtRecord().length == 1300);
+ }
+
+ @Test
+ public void testParcel() throws Exception {
+ NsdServiceInfo emptyInfo = new NsdServiceInfo();
+ checkParcelable(emptyInfo);
+
+ NsdServiceInfo fullInfo = new NsdServiceInfo();
+ fullInfo.setServiceName("kitten");
+ fullInfo.setServiceType("_kitten._tcp");
+ fullInfo.setPort(4242);
+ fullInfo.setHost(LOCALHOST);
+ checkParcelable(fullInfo);
+
+ NsdServiceInfo noHostInfo = new NsdServiceInfo();
+ noHostInfo.setServiceName("kitten");
+ noHostInfo.setServiceType("_kitten._tcp");
+ noHostInfo.setPort(4242);
+ checkParcelable(noHostInfo);
+
+ NsdServiceInfo attributedInfo = new NsdServiceInfo();
+ attributedInfo.setServiceName("kitten");
+ attributedInfo.setServiceType("_kitten._tcp");
+ attributedInfo.setPort(4242);
+ attributedInfo.setHost(LOCALHOST);
+ attributedInfo.setAttribute("color", "pink");
+ attributedInfo.setAttribute("sound", (new String("にゃあ")).getBytes("UTF-8"));
+ attributedInfo.setAttribute("adorable", (String) null);
+ attributedInfo.setAttribute("sticky", "yes");
+ attributedInfo.setAttribute("siblings", new byte[] {});
+ attributedInfo.setAttribute("edge cases", new byte[] {0, -1, 127, -128});
+ attributedInfo.removeAttribute("sticky");
+ checkParcelable(attributedInfo);
+
+ // Sanity check that we actually wrote attributes to attributedInfo.
+ assertTrue(attributedInfo.getAttributes().keySet().contains("adorable"));
+ String sound = new String(attributedInfo.getAttributes().get("sound"), "UTF-8");
+ assertTrue(sound.equals("にゃあ"));
+ byte[] edgeCases = attributedInfo.getAttributes().get("edge cases");
+ assertTrue(Arrays.equals(edgeCases, new byte[] {0, -1, 127, -128}));
+ assertFalse(attributedInfo.getAttributes().keySet().contains("sticky"));
+ }
+
+ public void checkParcelable(NsdServiceInfo original) {
+ // Write to parcel.
+ Parcel p = Parcel.obtain();
+ Bundle writer = new Bundle();
+ writer.putParcelable("test_info", original);
+ writer.writeToParcel(p, 0);
+
+ // Extract from parcel.
+ p.setDataPosition(0);
+ Bundle reader = p.readBundle();
+ reader.setClassLoader(NsdServiceInfo.class.getClassLoader());
+ NsdServiceInfo result = reader.getParcelable("test_info");
+
+ // Assert equality of base fields.
+ assertEquals(original.getServiceName(), result.getServiceName());
+ assertEquals(original.getServiceType(), result.getServiceType());
+ assertEquals(original.getHost(), result.getHost());
+ assertTrue(original.getPort() == result.getPort());
+
+ // Assert equality of attribute map.
+ Map<String, byte[]> originalMap = original.getAttributes();
+ Map<String, byte[]> resultMap = result.getAttributes();
+ assertEquals(originalMap.keySet(), resultMap.keySet());
+ for (String key : originalMap.keySet()) {
+ assertTrue(Arrays.equals(originalMap.get(key), resultMap.get(key)));
+ }
+ }
+
+ public void assertEmptyServiceInfo(NsdServiceInfo shouldBeEmpty) {
+ byte[] txtRecord = shouldBeEmpty.getTxtRecord();
+ if (txtRecord == null || txtRecord.length == 0) {
+ return;
+ }
+ fail("NsdServiceInfo.getTxtRecord did not return null but " + Arrays.toString(txtRecord));
+ }
+}
diff --git a/tests/unit/java/android/net/util/DnsUtilsTest.java b/tests/unit/java/android/net/util/DnsUtilsTest.java
new file mode 100644
index 0000000..b626db8
--- /dev/null
+++ b/tests/unit/java/android/net/util/DnsUtilsTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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.util;
+
+import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_GLOBAL;
+import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_LINKLOCAL;
+import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_SITELOCAL;
+
+import static org.junit.Assert.assertEquals;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DnsUtilsTest {
+ private InetAddress stringToAddress(@NonNull String addr) {
+ return InetAddresses.parseNumericAddress(addr);
+ }
+
+ private DnsUtils.SortableAddress makeSortableAddress(@NonNull String addr) {
+ return makeSortableAddress(addr, null);
+ }
+
+ private DnsUtils.SortableAddress makeSortableAddress(@NonNull String addr,
+ @Nullable String srcAddr) {
+ return new DnsUtils.SortableAddress(stringToAddress(addr),
+ srcAddr != null ? stringToAddress(srcAddr) : null);
+ }
+
+ @Test
+ public void testRfc6724Comparator() {
+ final List<DnsUtils.SortableAddress> test = Arrays.asList(
+ // Ipv4
+ makeSortableAddress("216.58.200.36", "192.168.1.1"),
+ // global with different scope src
+ makeSortableAddress("2404:6800:4008:801::2004", "fe80::1111:2222"),
+ // global without src addr
+ makeSortableAddress("2404:6800:cafe:801::1"),
+ // loop back
+ makeSortableAddress("::1", "::1"),
+ // link local
+ makeSortableAddress("fe80::c46f:1cff:fe04:39b4", "fe80::1"),
+ // teredo tunneling
+ makeSortableAddress("2001::47c1", "2001::2"),
+ // 6bone without src addr
+ makeSortableAddress("3ffe::1234:5678"),
+ // IPv4-compatible
+ makeSortableAddress("::216.58.200.36", "::216.58.200.9"),
+ // 6bone
+ makeSortableAddress("3ffe::1234:5678", "3ffe::1234:1"),
+ // IPv4-mapped IPv6
+ makeSortableAddress("::ffff:192.168.95.7", "::ffff:192.168.95.1"));
+
+ final List<InetAddress> expected = Arrays.asList(
+ stringToAddress("::1"), // loop back
+ stringToAddress("fe80::c46f:1cff:fe04:39b4"), // link local
+ stringToAddress("216.58.200.36"), // Ipv4
+ stringToAddress("::ffff:192.168.95.7"), // IPv4-mapped IPv6
+ stringToAddress("2001::47c1"), // teredo tunneling
+ stringToAddress("::216.58.200.36"), // IPv4-compatible
+ stringToAddress("3ffe::1234:5678"), // 6bone
+ stringToAddress("2404:6800:4008:801::2004"), // global with different scope src
+ stringToAddress("2404:6800:cafe:801::1"), // global without src addr
+ stringToAddress("3ffe::1234:5678")); // 6bone without src addr
+
+ Collections.sort(test, new DnsUtils.Rfc6724Comparator());
+
+ for (int i = 0; i < test.size(); ++i) {
+ assertEquals(test.get(i).address, expected.get(i));
+ }
+
+ // TODO: add more combinations
+ }
+
+ @Test
+ public void testV4SortableAddress() {
+ // Test V4 address
+ DnsUtils.SortableAddress test = makeSortableAddress("216.58.200.36");
+ assertEquals(test.hasSrcAddr, 0);
+ assertEquals(test.prefixMatchLen, 0);
+ assertEquals(test.address, stringToAddress("216.58.200.36"));
+ assertEquals(test.labelMatch, 0);
+ assertEquals(test.scopeMatch, 0);
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 4);
+ assertEquals(test.precedence, 35);
+
+ // Test V4 loopback address with the same source address
+ test = makeSortableAddress("127.1.2.3", "127.1.2.3");
+ assertEquals(test.hasSrcAddr, 1);
+ assertEquals(test.prefixMatchLen, 0);
+ assertEquals(test.address, stringToAddress("127.1.2.3"));
+ assertEquals(test.labelMatch, 1);
+ assertEquals(test.scopeMatch, 1);
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+ assertEquals(test.label, 4);
+ assertEquals(test.precedence, 35);
+ }
+
+ @Test
+ public void testV6SortableAddress() {
+ // Test global address
+ DnsUtils.SortableAddress test = makeSortableAddress("2404:6800:4008:801::2004");
+ assertEquals(test.address, stringToAddress("2404:6800:4008:801::2004"));
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 1);
+ assertEquals(test.precedence, 40);
+
+ // Test global address with global source address
+ test = makeSortableAddress("2404:6800:4008:801::2004",
+ "2401:fa00:fc:fd00:6d6c:7199:b8e7:41d6");
+ assertEquals(test.address, stringToAddress("2404:6800:4008:801::2004"));
+ assertEquals(test.hasSrcAddr, 1);
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.labelMatch, 1);
+ assertEquals(test.scopeMatch, 1);
+ assertEquals(test.label, 1);
+ assertEquals(test.precedence, 40);
+ assertEquals(test.prefixMatchLen, 13);
+
+ // Test global address with linklocal source address
+ test = makeSortableAddress("2404:6800:4008:801::2004", "fe80::c46f:1cff:fe04:39b4");
+ assertEquals(test.hasSrcAddr, 1);
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.labelMatch, 1);
+ assertEquals(test.scopeMatch, 0);
+ assertEquals(test.label, 1);
+ assertEquals(test.precedence, 40);
+ assertEquals(test.prefixMatchLen, 0);
+
+ // Test loopback address with the same source address
+ test = makeSortableAddress("::1", "::1");
+ assertEquals(test.hasSrcAddr, 1);
+ assertEquals(test.prefixMatchLen, 16 * 8);
+ assertEquals(test.labelMatch, 1);
+ assertEquals(test.scopeMatch, 1);
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+ assertEquals(test.label, 0);
+ assertEquals(test.precedence, 50);
+
+ // Test linklocal address
+ test = makeSortableAddress("fe80::c46f:1cff:fe04:39b4");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+ assertEquals(test.label, 1);
+ assertEquals(test.precedence, 40);
+
+ // Test linklocal address
+ test = makeSortableAddress("fe80::");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+ assertEquals(test.label, 1);
+ assertEquals(test.precedence, 40);
+
+ // Test 6to4 address
+ test = makeSortableAddress("2002:c000:0204::");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 2);
+ assertEquals(test.precedence, 30);
+
+ // Test unique local address
+ test = makeSortableAddress("fc00::c000:13ab");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 13);
+ assertEquals(test.precedence, 3);
+
+ // Test teredo tunneling address
+ test = makeSortableAddress("2001::47c1");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 5);
+ assertEquals(test.precedence, 5);
+
+ // Test IPv4-compatible addresses
+ test = makeSortableAddress("::216.58.200.36");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 3);
+ assertEquals(test.precedence, 1);
+
+ // Test site-local address
+ test = makeSortableAddress("fec0::cafe:3ab2");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_SITELOCAL);
+ assertEquals(test.label, 11);
+ assertEquals(test.precedence, 1);
+
+ // Test 6bone address
+ test = makeSortableAddress("3ffe::1234:5678");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 12);
+ assertEquals(test.precedence, 1);
+ }
+}
diff --git a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
new file mode 100644
index 0000000..5006d53
--- /dev/null
+++ b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.util
+
+import android.content.Context
+import android.content.res.Resources
+import android.net.ConnectivityResources
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.MAX_TRANSPORT
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkCapabilities.TRANSPORT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import org.junit.After
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+
+/**
+ * Tests for [KeepaliveUtils].
+ *
+ * Build, install and run with:
+ * atest android.net.util.KeepaliveUtilsTest
+ */
+@RunWith(JUnit4::class)
+@SmallTest
+class KeepaliveUtilsTest {
+
+ // Prepare mocked context with given resource strings.
+ private fun getMockedContextWithStringArrayRes(
+ id: Int,
+ name: String,
+ res: Array<out String?>?
+ ): Context {
+ val mockRes = mock(Resources::class.java)
+ doReturn(res).`when`(mockRes).getStringArray(eq(id))
+ doReturn(id).`when`(mockRes).getIdentifier(eq(name), any(), any())
+
+ return mock(Context::class.java).apply {
+ doReturn(mockRes).`when`(this).getResources()
+ ConnectivityResources.setResourcesContextForTest(this)
+ }
+ }
+
+ @After
+ fun tearDown() {
+ ConnectivityResources.setResourcesContextForTest(null)
+ }
+
+ @Test
+ fun testGetSupportedKeepalives() {
+ fun assertRunWithException(res: Array<out String?>?) {
+ try {
+ val mockContext = getMockedContextWithStringArrayRes(
+ R.array.config_networkSupportedKeepaliveCount,
+ "config_networkSupportedKeepaliveCount", res)
+ KeepaliveUtils.getSupportedKeepalives(mockContext)
+ fail("Expected KeepaliveDeviceConfigurationException")
+ } catch (expected: KeepaliveUtils.KeepaliveDeviceConfigurationException) {
+ }
+ }
+
+ // Check resource with various invalid format.
+ assertRunWithException(null)
+ assertRunWithException(arrayOf<String?>(null))
+ assertRunWithException(arrayOfNulls<String?>(10))
+ assertRunWithException(arrayOf(""))
+ assertRunWithException(arrayOf("3,ABC"))
+ assertRunWithException(arrayOf("6,3,3"))
+ assertRunWithException(arrayOf("5"))
+
+ // Check resource with invalid slots value.
+ assertRunWithException(arrayOf("3,-1"))
+
+ // Check resource with invalid transport type.
+ assertRunWithException(arrayOf("-1,3"))
+ assertRunWithException(arrayOf("10,3"))
+
+ // Check valid customization generates expected array.
+ val validRes = arrayOf("0,3", "1,0", "4,4")
+ val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0, 0)
+
+ val mockContext = getMockedContextWithStringArrayRes(
+ R.array.config_networkSupportedKeepaliveCount,
+ "config_networkSupportedKeepaliveCount", validRes)
+ val actual = KeepaliveUtils.getSupportedKeepalives(mockContext)
+ assertArrayEquals(expectedValidRes, actual)
+ }
+
+ @Test
+ fun testGetSupportedKeepalivesForNetworkCapabilities() {
+ // Mock customized supported keepalives for each transport type, and assuming:
+ // 3 for cellular,
+ // 6 for wifi,
+ // 0 for others.
+ val cust = IntArray(MAX_TRANSPORT + 1).apply {
+ this[TRANSPORT_CELLULAR] = 3
+ this[TRANSPORT_WIFI] = 6
+ }
+
+ val nc = NetworkCapabilities()
+ // Check supported keepalives with single transport type.
+ nc.transportTypes = intArrayOf(TRANSPORT_CELLULAR)
+ assertEquals(3, KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc))
+
+ // Check supported keepalives with multiple transport types.
+ nc.transportTypes = intArrayOf(TRANSPORT_WIFI, TRANSPORT_VPN)
+ assertEquals(0, KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc))
+
+ // Check supported keepalives with non-customized transport type.
+ nc.transportTypes = intArrayOf(TRANSPORT_ETHERNET)
+ assertEquals(0, KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc))
+
+ // Check supported keepalives with undefined transport type.
+ nc.transportTypes = intArrayOf(MAX_TRANSPORT + 1)
+ try {
+ KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc)
+ fail("Expected ArrayIndexOutOfBoundsException")
+ } catch (expected: ArrayIndexOutOfBoundsException) {
+ }
+ }
+}
diff --git a/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt b/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt
new file mode 100644
index 0000000..25aa626
--- /dev/null
+++ b/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2021 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.util
+
+import android.content.Context
+import android.content.res.Resources
+import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER
+import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE
+import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY
+import android.net.ConnectivityResources
+import android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI
+import android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE
+import android.net.util.MultinetworkPolicyTracker.ActiveDataSubscriptionIdListener
+import android.provider.Settings
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
+import android.test.mock.MockContentResolver
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.connectivity.resources.R
+import com.android.internal.util.test.FakeSettingsProvider
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+/**
+ * Tests for [MultinetworkPolicyTracker].
+ *
+ * Build, install and run with:
+ * atest android.net.util.MultinetworkPolicyTrackerTest
+ */
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class MultinetworkPolicyTrackerTest {
+ private val resources = mock(Resources::class.java).also {
+ doReturn(R.integer.config_networkAvoidBadWifi).`when`(it).getIdentifier(
+ eq("config_networkAvoidBadWifi"), eq("integer"), any())
+ doReturn(0).`when`(it).getInteger(R.integer.config_networkAvoidBadWifi)
+ }
+ private val telephonyManager = mock(TelephonyManager::class.java)
+ private val subscriptionManager = mock(SubscriptionManager::class.java).also {
+ doReturn(null).`when`(it).getActiveSubscriptionInfo(anyInt())
+ }
+ private val resolver = MockContentResolver().apply {
+ addProvider(Settings.AUTHORITY, FakeSettingsProvider()) }
+ private val context = mock(Context::class.java).also {
+ doReturn(Context.TELEPHONY_SERVICE).`when`(it)
+ .getSystemServiceName(TelephonyManager::class.java)
+ doReturn(telephonyManager).`when`(it).getSystemService(Context.TELEPHONY_SERVICE)
+ doReturn(subscriptionManager).`when`(it)
+ .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
+ doReturn(resolver).`when`(it).contentResolver
+ doReturn(resources).`when`(it).resources
+ doReturn(it).`when`(it).createConfigurationContext(any())
+ Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "1")
+ ConnectivityResources.setResourcesContextForTest(it)
+ }
+ private val tracker = MultinetworkPolicyTracker(context, null /* handler */)
+
+ private fun assertMultipathPreference(preference: Int) {
+ Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
+ preference.toString())
+ tracker.updateMeteredMultipathPreference()
+ assertEquals(preference, tracker.meteredMultipathPreference)
+ }
+
+ @After
+ fun tearDown() {
+ ConnectivityResources.setResourcesContextForTest(null)
+ }
+
+ @Test
+ fun testUpdateMeteredMultipathPreference() {
+ assertMultipathPreference(MULTIPATH_PREFERENCE_HANDOVER)
+ assertMultipathPreference(MULTIPATH_PREFERENCE_RELIABILITY)
+ assertMultipathPreference(MULTIPATH_PREFERENCE_PERFORMANCE)
+ }
+
+ @Test
+ fun testUpdateAvoidBadWifi() {
+ Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0")
+ assertTrue(tracker.updateAvoidBadWifi())
+ assertFalse(tracker.avoidBadWifi)
+
+ doReturn(1).`when`(resources).getInteger(R.integer.config_networkAvoidBadWifi)
+ assertTrue(tracker.updateAvoidBadWifi())
+ assertTrue(tracker.avoidBadWifi)
+ }
+
+ @Test
+ fun testOnActiveDataSubscriptionIdChanged() {
+ val testSubId = 1000
+ val subscriptionInfo = SubscriptionInfo(testSubId, ""/* iccId */, 1/* iccId */,
+ "TMO"/* displayName */, "TMO"/* carrierName */, 1/* nameSource */, 1/* iconTint */,
+ "123"/* number */, 1/* roaming */, null/* icon */, "310"/* mcc */, "210"/* mnc */,
+ ""/* countryIso */, false/* isEmbedded */, null/* nativeAccessRules */,
+ "1"/* cardString */)
+ doReturn(subscriptionInfo).`when`(subscriptionManager).getActiveSubscriptionInfo(testSubId)
+
+ // Modify avoidBadWifi and meteredMultipathPreference settings value and local variables in
+ // MultinetworkPolicyTracker should be also updated after subId changed.
+ Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0")
+ Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
+ MULTIPATH_PREFERENCE_PERFORMANCE.toString())
+
+ val listenerCaptor = ArgumentCaptor.forClass(
+ ActiveDataSubscriptionIdListener::class.java)
+ verify(telephonyManager, times(1))
+ .registerTelephonyCallback(any(), listenerCaptor.capture())
+ val listener = listenerCaptor.value
+ listener.onActiveDataSubscriptionIdChanged(testSubId)
+
+ // Check it get resource value with test sub id.
+ verify(subscriptionManager, times(1)).getActiveSubscriptionInfo(testSubId)
+ verify(context).createConfigurationContext(argThat { it.mcc == 310 && it.mnc == 210 })
+
+ // Check if avoidBadWifi and meteredMultipathPreference values have been updated.
+ assertFalse(tracker.avoidBadWifi)
+ assertEquals(MULTIPATH_PREFERENCE_PERFORMANCE, tracker.meteredMultipathPreference)
+ }
+}