Merge client-libs/ from platform/frameworks/libs/net/ to staticlibs/client-libs/
diff --git a/staticlibs/client-libs/Android.bp b/staticlibs/client-libs/Android.bp
new file mode 100644
index 0000000..cabfa06
--- /dev/null
+++ b/staticlibs/client-libs/Android.bp
@@ -0,0 +1,28 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+ name: "netd-client",
+ srcs: ["netd/**/*"],
+ sdk_version: "system_current",
+ min_sdk_version: "29",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.tethering",
+ "com.android.wifi"
+ ],
+ visibility: [
+ "//packages/modules/Connectivity:__subpackages__",
+ "//frameworks/base/services:__subpackages__",
+ "//frameworks/base/packages:__subpackages__",
+ "//frameworks/libs/net/client-libs/tests:__subpackages__",
+ "//frameworks/libs/net/common:__subpackages__",
+ "//packages/modules/Wifi/service:__subpackages__"
+ ],
+ libs: ["androidx.annotation_annotation"],
+ static_libs: [
+ "netd_aidl_interface-lateststable-java",
+ "netd_event_listener_interface-lateststable-java"
+ ]
+}
diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdEventListener.java b/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdEventListener.java
new file mode 100644
index 0000000..4180732
--- /dev/null
+++ b/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdEventListener.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.net.module.util;
+
+import android.net.metrics.INetdEventListener;
+
+/**
+ * Base {@link INetdEventListener} that provides no-op implementations which can
+ * be overridden.
+ */
+public class BaseNetdEventListener extends INetdEventListener.Stub {
+
+ @Override
+ public void onDnsEvent(int netId, int eventType, int returnCode,
+ int latencyMs, String hostname, String[] ipAddresses,
+ int ipAddressesCount, int uid) { }
+
+ @Override
+ public void onPrivateDnsValidationEvent(int netId, String ipAddress,
+ String hostname, boolean validated) { }
+
+ @Override
+ public void onConnectEvent(int netId, int error, int latencyMs,
+ String ipAddr, int port, int uid) { }
+
+ @Override
+ public void onWakeupEvent(String prefix, int uid, int ethertype,
+ int ipNextHeader, byte[] dstHw, String srcIp, String dstIp,
+ int srcPort, int dstPort, long timestampNs) { }
+
+ @Override
+ public void onTcpSocketStatsEvent(int[] networkIds, int[] sentPackets,
+ int[] lostPackets, int[] rttUs, int[] sentAckDiffMs) { }
+
+ @Override
+ public void onNat64PrefixEvent(int netId, boolean added,
+ String prefixString, int prefixLength) { }
+
+ @Override
+ public int getInterfaceVersion() {
+ return INetdEventListener.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ return INetdEventListener.HASH;
+ }
+}
diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdUnsolicitedEventListener.java b/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdUnsolicitedEventListener.java
new file mode 100644
index 0000000..526dd8b
--- /dev/null
+++ b/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdUnsolicitedEventListener.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.net.module.util;
+
+import android.net.INetdUnsolicitedEventListener;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Base {@link INetdUnsolicitedEventListener} that provides no-op implementations which can be
+ * overridden.
+ */
+public class BaseNetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub {
+
+ @Override
+ public void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs,
+ int uid) { }
+
+ @Override
+ public void onQuotaLimitReached(@NonNull String alertName, @NonNull String ifName) { }
+
+ @Override
+ public void onInterfaceDnsServerInfo(@NonNull String ifName, long lifetimeS,
+ @NonNull String[] servers) { }
+
+ @Override
+ public void onInterfaceAddressUpdated(@NonNull String addr, String ifName, int flags,
+ int scope) { }
+
+ @Override
+ public void onInterfaceAddressRemoved(@NonNull String addr, @NonNull String ifName, int flags,
+ int scope) { }
+
+ @Override
+ public void onInterfaceAdded(@NonNull String ifName) { }
+
+ @Override
+ public void onInterfaceRemoved(@NonNull String ifName) { }
+
+ @Override
+ public void onInterfaceChanged(@NonNull String ifName, boolean up) { }
+
+ @Override
+ public void onInterfaceLinkStateChanged(@NonNull String ifName, boolean up) { }
+
+ @Override
+ public void onRouteChanged(boolean updated, @NonNull String route, @NonNull String gateway,
+ @NonNull String ifName) { }
+
+ @Override
+ public void onStrictCleartextDetected(int uid, @NonNull String hex) { }
+
+ @Override
+ public int getInterfaceVersion() {
+ return INetdUnsolicitedEventListener.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ return INetdUnsolicitedEventListener.HASH;
+ }
+}
diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
new file mode 100644
index 0000000..98fda56
--- /dev/null
+++ b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
@@ -0,0 +1,279 @@
+/*
+ * 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 com.android.net.module.util;
+
+import static android.net.INetd.IF_STATE_DOWN;
+import static android.net.INetd.IF_STATE_UP;
+import static android.net.RouteInfo.RTN_THROW;
+import static android.net.RouteInfo.RTN_UNICAST;
+import static android.net.RouteInfo.RTN_UNREACHABLE;
+import static android.system.OsConstants.EBUSY;
+
+import android.annotation.SuppressLint;
+import android.net.INetd;
+import android.net.InterfaceConfigurationParcel;
+import android.net.IpPrefix;
+import android.net.RouteInfo;
+import android.net.TetherConfigParcel;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Collection of utilities for netd.
+ */
+public class NetdUtils {
+ private static final String TAG = NetdUtils.class.getSimpleName();
+
+ /** Used to modify the specified route. */
+ public enum ModifyOperation {
+ ADD,
+ REMOVE,
+ }
+
+ /**
+ * Get InterfaceConfigurationParcel from netd.
+ */
+ public static InterfaceConfigurationParcel getInterfaceConfigParcel(@NonNull INetd netd,
+ @NonNull String iface) {
+ try {
+ return netd.interfaceGetCfg(iface);
+ } catch (RemoteException | ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static void validateFlag(String flag) {
+ if (flag.indexOf(' ') >= 0) {
+ throw new IllegalArgumentException("flag contains space: " + flag);
+ }
+ }
+
+ /**
+ * Check whether the InterfaceConfigurationParcel contains the target flag or not.
+ *
+ * @param config The InterfaceConfigurationParcel instance.
+ * @param flag Target flag string to be checked.
+ */
+ public static boolean hasFlag(@NonNull final InterfaceConfigurationParcel config,
+ @NonNull final String flag) {
+ validateFlag(flag);
+ final Set<String> flagList = new HashSet<String>(Arrays.asList(config.flags));
+ return flagList.contains(flag);
+ }
+
+ @VisibleForTesting
+ protected static String[] removeAndAddFlags(@NonNull String[] flags, @NonNull String remove,
+ @NonNull String add) {
+ final ArrayList<String> result = new ArrayList<>();
+ try {
+ // Validate the add flag first, so that the for-loop can be ignore once the format of
+ // add flag is invalid.
+ validateFlag(add);
+ for (String flag : flags) {
+ // Simply ignore both of remove and add flags first, then add the add flag after
+ // exiting the loop to prevent adding the duplicate flag.
+ if (remove.equals(flag) || add.equals(flag)) continue;
+ result.add(flag);
+ }
+ result.add(add);
+ return result.toArray(new String[result.size()]);
+ } catch (IllegalArgumentException iae) {
+ throw new IllegalStateException("Invalid InterfaceConfigurationParcel", iae);
+ }
+ }
+
+ /**
+ * Set interface configuration to netd by passing InterfaceConfigurationParcel.
+ */
+ public static void setInterfaceConfig(INetd netd, InterfaceConfigurationParcel configParcel) {
+ try {
+ netd.interfaceSetCfg(configParcel);
+ } catch (RemoteException | ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Set the given interface up.
+ */
+ public static void setInterfaceUp(INetd netd, String iface) {
+ final InterfaceConfigurationParcel configParcel = getInterfaceConfigParcel(netd, iface);
+ configParcel.flags = removeAndAddFlags(configParcel.flags, IF_STATE_DOWN /* remove */,
+ IF_STATE_UP /* add */);
+ setInterfaceConfig(netd, configParcel);
+ }
+
+ /**
+ * Set the given interface down.
+ */
+ public static void setInterfaceDown(INetd netd, String iface) {
+ final InterfaceConfigurationParcel configParcel = getInterfaceConfigParcel(netd, iface);
+ configParcel.flags = removeAndAddFlags(configParcel.flags, IF_STATE_UP /* remove */,
+ IF_STATE_DOWN /* add */);
+ setInterfaceConfig(netd, configParcel);
+ }
+
+ /** Start tethering. */
+ public static void tetherStart(final INetd netd, final boolean usingLegacyDnsProxy,
+ final String[] dhcpRange) throws RemoteException, ServiceSpecificException {
+ final TetherConfigParcel config = new TetherConfigParcel();
+ config.usingLegacyDnsProxy = usingLegacyDnsProxy;
+ config.dhcpRanges = dhcpRange;
+ netd.tetherStartWithConfiguration(config);
+ }
+
+ /** Setup interface for tethering. */
+ public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest)
+ throws RemoteException, ServiceSpecificException {
+ tetherInterface(netd, iface, dest, 20 /* maxAttempts */, 50 /* pollingIntervalMs */);
+ }
+
+ /** Setup interface with configurable retries for tethering. */
+ public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest,
+ int maxAttempts, int pollingIntervalMs)
+ throws RemoteException, ServiceSpecificException {
+ netd.tetherInterfaceAdd(iface);
+ networkAddInterface(netd, iface, maxAttempts, pollingIntervalMs);
+ List<RouteInfo> routes = new ArrayList<>();
+ routes.add(new RouteInfo(dest, null, iface, RTN_UNICAST));
+ addRoutesToLocalNetwork(netd, iface, routes);
+ }
+
+ /**
+ * Retry Netd#networkAddInterface for EBUSY error code.
+ * If the same interface (e.g., wlan0) is in client mode and then switches to tethered mode.
+ * There can be a race where puts the interface into the local network but interface is still
+ * in use in netd because the ConnectivityService thread hasn't processed the disconnect yet.
+ * See b/158269544 for detail.
+ */
+ private static void networkAddInterface(final INetd netd, final String iface,
+ int maxAttempts, int pollingIntervalMs)
+ throws ServiceSpecificException, RemoteException {
+ for (int i = 1; i <= maxAttempts; i++) {
+ try {
+ netd.networkAddInterface(INetd.LOCAL_NET_ID, iface);
+ return;
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode == EBUSY && i < maxAttempts) {
+ SystemClock.sleep(pollingIntervalMs);
+ continue;
+ }
+
+ Log.e(TAG, "Retry Netd#networkAddInterface failure: " + e);
+ throw e;
+ }
+ }
+ }
+
+ /** Reset interface for tethering. */
+ public static void untetherInterface(final INetd netd, String iface)
+ throws RemoteException, ServiceSpecificException {
+ try {
+ netd.tetherInterfaceRemove(iface);
+ } finally {
+ netd.networkRemoveInterface(INetd.LOCAL_NET_ID, iface);
+ }
+ }
+
+ /** Add |routes| to local network. */
+ public static void addRoutesToLocalNetwork(final INetd netd, final String iface,
+ final List<RouteInfo> routes) {
+
+ for (RouteInfo route : routes) {
+ if (!route.isDefaultRoute()) {
+ modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, route);
+ }
+ }
+
+ // IPv6 link local should be activated always.
+ modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID,
+ new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST));
+ }
+
+ /** Remove routes from local network. */
+ public static int removeRoutesFromLocalNetwork(final INetd netd, final List<RouteInfo> routes) {
+ int failures = 0;
+
+ for (RouteInfo route : routes) {
+ try {
+ modifyRoute(netd, ModifyOperation.REMOVE, INetd.LOCAL_NET_ID, route);
+ } catch (IllegalStateException e) {
+ failures++;
+ }
+ }
+
+ return failures;
+ }
+
+ @SuppressLint("NewApi")
+ private static String findNextHop(final RouteInfo route) {
+ final String nextHop;
+ switch (route.getType()) {
+ case RTN_UNICAST:
+ if (route.hasGateway()) {
+ nextHop = route.getGateway().getHostAddress();
+ } else {
+ nextHop = INetd.NEXTHOP_NONE;
+ }
+ break;
+ case RTN_UNREACHABLE:
+ nextHop = INetd.NEXTHOP_UNREACHABLE;
+ break;
+ case RTN_THROW:
+ nextHop = INetd.NEXTHOP_THROW;
+ break;
+ default:
+ nextHop = INetd.NEXTHOP_NONE;
+ break;
+ }
+ return nextHop;
+ }
+
+ /** Add or remove |route|. */
+ public static void modifyRoute(final INetd netd, final ModifyOperation op, final int netId,
+ final RouteInfo route) {
+ final String ifName = route.getInterface();
+ final String dst = route.getDestination().toString();
+ final String nextHop = findNextHop(route);
+
+ try {
+ switch(op) {
+ case ADD:
+ netd.networkAddRoute(netId, ifName, dst, nextHop);
+ break;
+ case REMOVE:
+ netd.networkRemoveRoute(netId, ifName, dst, nextHop);
+ break;
+ default:
+ throw new IllegalStateException("Unsupported modify operation:" + op);
+ }
+ } catch (RemoteException | ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/staticlibs/client-libs/tests/unit/Android.bp b/staticlibs/client-libs/tests/unit/Android.bp
new file mode 100644
index 0000000..220a6c1
--- /dev/null
+++ b/staticlibs/client-libs/tests/unit/Android.bp
@@ -0,0 +1,44 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+ name: "NetdStaticLibTestsLib",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ min_sdk_version: "29",
+ static_libs: [
+ "androidx.test.rules",
+ "mockito-target-extended-minus-junit4",
+ "net-tests-utils-host-device-common",
+ "netd-client",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ visibility: [
+ // Visible for Tethering and NetworkStack integration test and link NetdStaticLibTestsLib
+ // there, so that the tests under client-libs can also be run when running tethering and
+ // NetworkStack MTS.
+ "//packages/modules/Connectivity/tests:__subpackages__",
+ "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
+ "//packages/modules/NetworkStack/tests/integration",
+ ]
+}
+
+android_test {
+ name: "NetdStaticLibTests",
+ certificate: "platform",
+ static_libs: [
+ "NetdStaticLibTestsLib",
+ ],
+ jni_libs: [
+ // For mockito extended
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+ test_suites: ["device-tests"],
+}
diff --git a/staticlibs/client-libs/tests/unit/AndroidManifest.xml b/staticlibs/client-libs/tests/unit/AndroidManifest.xml
new file mode 100644
index 0000000..7a07d3d
--- /dev/null
+++ b/staticlibs/client-libs/tests/unit/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.clientlibs.tests">
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.frameworks.clientlibs.tests"
+ android:label="Netd Static Library Tests" />
+</manifest>
diff --git a/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java b/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java
new file mode 100644
index 0000000..5069672
--- /dev/null
+++ b/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java
@@ -0,0 +1,276 @@
+/*
+ * 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 com.android.net.module.util;
+
+import static android.net.INetd.LOCAL_NET_ID;
+import static android.system.OsConstants.EBUSY;
+
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+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 static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.net.INetd;
+import android.net.InterfaceConfigurationParcel;
+import android.net.IpPrefix;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+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;
+
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetdUtilsTest {
+ @Mock private INetd mNetd;
+
+ private static final String IFACE = "TEST_IFACE";
+ private static final IpPrefix TEST_IPPREFIX = new IpPrefix("192.168.42.1/24");
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ private void setupFlagsForInterfaceConfiguration(String[] flags) throws Exception {
+ final InterfaceConfigurationParcel config = new InterfaceConfigurationParcel();
+ config.flags = flags;
+ when(mNetd.interfaceGetCfg(eq(IFACE))).thenReturn(config);
+ }
+
+ private void verifyMethodsAndArgumentsOfSetInterface(boolean ifaceUp) throws Exception {
+ final String[] flagsContainDownAndUp = new String[] {"flagA", "down", "flagB", "up"};
+ final String[] flagsForInterfaceDown = new String[] {"flagA", "down", "flagB"};
+ final String[] flagsForInterfaceUp = new String[] {"flagA", "up", "flagB"};
+ final String[] expectedFinalFlags;
+ setupFlagsForInterfaceConfiguration(flagsContainDownAndUp);
+ if (ifaceUp) {
+ // "down" flag will be removed from flagsContainDownAndUp when interface is up. Set
+ // expectedFinalFlags to flagsForInterfaceUp.
+ expectedFinalFlags = flagsForInterfaceUp;
+ NetdUtils.setInterfaceUp(mNetd, IFACE);
+ } else {
+ // "up" flag will be removed from flagsContainDownAndUp when interface is down. Set
+ // expectedFinalFlags to flagsForInterfaceDown.
+ expectedFinalFlags = flagsForInterfaceDown;
+ NetdUtils.setInterfaceDown(mNetd, IFACE);
+ }
+ verify(mNetd).interfaceSetCfg(
+ argThat(config ->
+ // Check if actual flags are the same as expected flags.
+ // TODO: Have a function in MiscAsserts to check if two arrays are the same.
+ CollectionUtils.all(Arrays.asList(expectedFinalFlags),
+ flag -> Arrays.asList(config.flags).contains(flag))
+ && CollectionUtils.all(Arrays.asList(config.flags),
+ flag -> Arrays.asList(expectedFinalFlags).contains(flag))));
+ }
+
+ @Test
+ public void testSetInterfaceUp() throws Exception {
+ verifyMethodsAndArgumentsOfSetInterface(true /* ifaceUp */);
+ }
+
+ @Test
+ public void testSetInterfaceDown() throws Exception {
+ verifyMethodsAndArgumentsOfSetInterface(false /* ifaceUp */);
+ }
+
+ @Test
+ public void testRemoveAndAddFlags() throws Exception {
+ final String[] flags = new String[] {"flagA", "down", "flagB"};
+ // Add an invalid flag and expect to get an IllegalStateException.
+ assertThrows(IllegalStateException.class,
+ () -> NetdUtils.removeAndAddFlags(flags, "down" /* remove */, "u p" /* add */));
+ }
+
+ private void setNetworkAddInterfaceOutcome(final Exception cause, int numLoops)
+ throws Exception {
+ // This cannot be an int because local variables referenced from a lambda expression must
+ // be final or effectively final.
+ final Counter myCounter = new Counter();
+ doAnswer((invocation) -> {
+ myCounter.count();
+ if (myCounter.isCounterReached(numLoops)) {
+ if (cause == null) return null;
+
+ throw cause;
+ }
+
+ throw new ServiceSpecificException(EBUSY);
+ }).when(mNetd).networkAddInterface(LOCAL_NET_ID, IFACE);
+ }
+
+ class Counter {
+ private int mValue = 0;
+
+ private void count() {
+ mValue++;
+ }
+
+ private boolean isCounterReached(int target) {
+ return mValue >= target;
+ }
+ }
+
+ @Test
+ public void testTetherInterfaceSuccessful() throws Exception {
+ // Expect #networkAddInterface successful at first tries.
+ verifyTetherInterfaceSucceeds(1);
+
+ // Expect #networkAddInterface successful after 10 tries.
+ verifyTetherInterfaceSucceeds(10);
+ }
+
+ private void runTetherInterfaceWithServiceSpecificException(int expectedTries,
+ int expectedCode) throws Exception {
+ setNetworkAddInterfaceOutcome(new ServiceSpecificException(expectedCode), expectedTries);
+
+ try {
+ NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX, 20, 0);
+ fail("Expect throw ServiceSpecificException");
+ } catch (ServiceSpecificException e) {
+ assertEquals(e.errorCode, expectedCode);
+ }
+
+ verifyNetworkAddInterfaceFails(expectedTries);
+ reset(mNetd);
+ }
+
+ private void runTetherInterfaceWithRemoteException(int expectedTries) throws Exception {
+ setNetworkAddInterfaceOutcome(new RemoteException(), expectedTries);
+
+ try {
+ NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX, 20, 0);
+ fail("Expect throw RemoteException");
+ } catch (RemoteException e) { }
+
+ verifyNetworkAddInterfaceFails(expectedTries);
+ reset(mNetd);
+ }
+
+ private void verifyNetworkAddInterfaceFails(int expectedTries) throws Exception {
+ verify(mNetd).tetherInterfaceAdd(IFACE);
+ verify(mNetd, times(expectedTries)).networkAddInterface(LOCAL_NET_ID, IFACE);
+ verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), any(), any());
+ verifyNoMoreInteractions(mNetd);
+ }
+
+ private void verifyTetherInterfaceSucceeds(int expectedTries) throws Exception {
+ setNetworkAddInterfaceOutcome(null, expectedTries);
+
+ NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX);
+ verify(mNetd).tetherInterfaceAdd(IFACE);
+ verify(mNetd, times(expectedTries)).networkAddInterface(LOCAL_NET_ID, IFACE);
+ verify(mNetd, times(2)).networkAddRoute(eq(LOCAL_NET_ID), eq(IFACE), any(), any());
+ verifyNoMoreInteractions(mNetd);
+ reset(mNetd);
+ }
+
+ @Test
+ public void testTetherInterfaceFailOnNetworkAddInterface() throws Exception {
+ // Test throwing ServiceSpecificException with EBUSY failure.
+ runTetherInterfaceWithServiceSpecificException(20, EBUSY);
+
+ // Test throwing ServiceSpecificException with unexpectedError.
+ final int unexpectedError = 999;
+ runTetherInterfaceWithServiceSpecificException(1, unexpectedError);
+
+ // Test throwing ServiceSpecificException with unexpectedError after 7 tries.
+ runTetherInterfaceWithServiceSpecificException(7, unexpectedError);
+
+ // Test throwing RemoteException.
+ runTetherInterfaceWithRemoteException(1);
+
+ // Test throwing RemoteException after 3 tries.
+ runTetherInterfaceWithRemoteException(3);
+ }
+
+ @Test
+ public void testNetdUtilsHasFlag() throws Exception {
+ final String[] flags = new String[] {"up", "broadcast", "running", "multicast"};
+ setupFlagsForInterfaceConfiguration(flags);
+
+ // Set interface up.
+ NetdUtils.setInterfaceUp(mNetd, IFACE);
+ final ArgumentCaptor<InterfaceConfigurationParcel> arg =
+ ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
+ verify(mNetd, times(1)).interfaceSetCfg(arg.capture());
+
+ final InterfaceConfigurationParcel p = arg.getValue();
+ assertTrue(NetdUtils.hasFlag(p, "up"));
+ assertTrue(NetdUtils.hasFlag(p, "running"));
+ assertTrue(NetdUtils.hasFlag(p, "broadcast"));
+ assertTrue(NetdUtils.hasFlag(p, "multicast"));
+ assertFalse(NetdUtils.hasFlag(p, "down"));
+ }
+
+ @Test
+ public void testNetdUtilsHasFlag_flagContainsSpace() throws Exception {
+ final String[] flags = new String[] {"up", "broadcast", "running", "multicast"};
+ setupFlagsForInterfaceConfiguration(flags);
+
+ // Set interface up.
+ NetdUtils.setInterfaceUp(mNetd, IFACE);
+ final ArgumentCaptor<InterfaceConfigurationParcel> arg =
+ ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
+ verify(mNetd, times(1)).interfaceSetCfg(arg.capture());
+
+ final InterfaceConfigurationParcel p = arg.getValue();
+ assertThrows(IllegalArgumentException.class, () -> NetdUtils.hasFlag(p, "up "));
+ }
+
+ @Test
+ public void testNetdUtilsHasFlag_UppercaseString() throws Exception {
+ final String[] flags = new String[] {"up", "broadcast", "running", "multicast"};
+ setupFlagsForInterfaceConfiguration(flags);
+
+ // Set interface up.
+ NetdUtils.setInterfaceUp(mNetd, IFACE);
+ final ArgumentCaptor<InterfaceConfigurationParcel> arg =
+ ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
+ verify(mNetd, times(1)).interfaceSetCfg(arg.capture());
+
+ final InterfaceConfigurationParcel p = arg.getValue();
+ assertFalse(NetdUtils.hasFlag(p, "UP"));
+ assertFalse(NetdUtils.hasFlag(p, "BROADCAST"));
+ assertFalse(NetdUtils.hasFlag(p, "RUNNING"));
+ assertFalse(NetdUtils.hasFlag(p, "MULTICAST"));
+ }
+}