CTS test for Eth network management APIs

CTS tests for EthernetManager APIs:
    #updateConfiguration

Bug: 236420528
Bug: 210487893
Test: atest CtsNetTestCases
:android.net.cts.EthernetManagerTest --iterations

Change-Id: I394cd306c0f78973564f9196834e690e4a51a3fd
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index d72cf1f..89b107e 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -21,9 +21,9 @@
 import android.content.Context
 import android.net.ConnectivityManager
 import android.net.EthernetManager
-import android.net.EthernetManager.InterfaceStateListener
 import android.net.EthernetManager.ETHERNET_STATE_DISABLED
 import android.net.EthernetManager.ETHERNET_STATE_ENABLED
+import android.net.EthernetManager.InterfaceStateListener
 import android.net.EthernetManager.ROLE_CLIENT
 import android.net.EthernetManager.ROLE_NONE
 import android.net.EthernetManager.ROLE_SERVER
@@ -37,13 +37,16 @@
 import android.net.EthernetNetworkUpdateRequest
 import android.net.InetAddresses
 import android.net.IpConfiguration
+import android.net.LinkAddress
 import android.net.MacAddress
 import android.net.Network
 import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
 import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
 import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
 import android.net.NetworkCapabilities.TRANSPORT_TEST
 import android.net.NetworkRequest
+import android.net.StaticIpConfiguration
 import android.net.TestNetworkInterface
 import android.net.TestNetworkManager
 import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.EthernetStateChanged
@@ -57,16 +60,17 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.net.module.util.ArrayTrackRecord
 import com.android.net.module.util.TrackRecord
-import com.android.testutils.anyNetwork
 import com.android.testutils.ConnectivityModuleTest
-import com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast
 import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
 import com.android.testutils.RecorderCallback.CallbackEntry.Lost
 import com.android.testutils.RouterAdvertisementResponder
 import com.android.testutils.TapPacketReader
 import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.anyNetwork
 import com.android.testutils.runAsShell
 import com.android.testutils.waitForIdle
 import org.junit.After
@@ -77,8 +81,8 @@
 import java.net.Inet6Address
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.ExecutionException
-import java.util.concurrent.TimeoutException
 import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
 import java.util.function.IntConsumer
 import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
@@ -88,17 +92,25 @@
 import kotlin.test.assertTrue
 import kotlin.test.fail
 
+private const val TAG = "EthernetManagerTest"
 // TODO: try to lower this timeout in the future. Currently, ethernet tests are still flaky because
 // the interface is not ready fast enough (mostly due to the up / up / down / up issue).
 private const val TIMEOUT_MS = 2000L
-private const val NO_CALLBACK_TIMEOUT_MS = 200L
+// Timeout used to confirm no callbacks matching given criteria are received. Must be long enough to
+// process all callbacks including ip provisioning when using the updateConfiguration API.
+private const val NO_CALLBACK_TIMEOUT_MS = 500L
+
 private val DEFAULT_IP_CONFIGURATION = IpConfiguration(IpConfiguration.IpAssignment.DHCP,
-    IpConfiguration.ProxySettings.NONE, null, null)
+        IpConfiguration.ProxySettings.NONE, null, null)
 private val ETH_REQUEST: NetworkRequest = NetworkRequest.Builder()
-    .addTransportType(TRANSPORT_TEST)
-    .addTransportType(TRANSPORT_ETHERNET)
-    .removeCapability(NET_CAPABILITY_TRUSTED)
-    .build()
+        .addTransportType(TRANSPORT_TEST)
+        .addTransportType(TRANSPORT_ETHERNET)
+        .removeCapability(NET_CAPABILITY_TRUSTED)
+        .build()
+private val STATIC_IP_CONFIGURATION = IpConfiguration.Builder()
+        .setStaticIpConfiguration(StaticIpConfiguration.Builder()
+                .setIpAddress(LinkAddress("192.0.2.1/30")).build())
+        .build()
 
 @AppModeFull(reason = "Instant apps can't access EthernetManager")
 // EthernetManager is not updatable before T, so tests do not need to be backwards compatible.
@@ -436,7 +448,10 @@
 
     // It can take multiple seconds for the network to become available.
     private fun TestableNetworkCallback.expectAvailable() =
-        expectCallback<Available>(anyNetwork(), 5000 /* ms timeout */).network
+            expectCallback<Available>(anyNetwork(), 5000 /* ms timeout */).network
+
+    private fun TestableNetworkCallback.expectLost(n: Network = anyNetwork()) =
+            expectCallback<Lost>(n, 5000 /* ms timeout */)
 
     // b/233534110: eventuallyExpect<Lost>() does not advance ReadHead, use
     // eventuallyExpect(Lost::class) instead.
@@ -444,7 +459,9 @@
         eventuallyExpect(Lost::class, TIMEOUT_MS) { n?.equals(it.network) ?: true }
 
     private fun TestableNetworkCallback.assertNeverLost(n: Network? = null) =
-        assertNoCallbackThat() { it is Lost && (n?.equals(it.network) ?: true) }
+        assertNoCallbackThat(NO_CALLBACK_TIMEOUT_MS) {
+            it is Lost && (n?.equals(it.network) ?: true)
+        }
 
     private fun TestableNetworkCallback.assertNeverAvailable(n: Network? = null) =
         assertNoCallbackThat() { it is Available && (n?.equals(it.network) ?: true) }
@@ -454,6 +471,18 @@
             it.networkSpecifier == EthernetNetworkSpecifier(name)
         }
 
+    private fun TestableNetworkCallback.expectCapabilitiesWithCapability(cap: Int) =
+        expectCapabilitiesThat(anyNetwork(), TIMEOUT_MS) {
+            it.hasCapability(cap)
+        }
+
+    private fun TestableNetworkCallback.expectLinkPropertiesWithLinkAddress(addr: LinkAddress) =
+        expectLinkPropertiesThat(anyNetwork(), TIMEOUT_MS) {
+            // LinkAddress.equals isn't possible as the system changes the LinkAddress.flags value.
+            // any() must be used since the interface may also have a link-local address.
+            it.linkAddresses.any { x -> x.isSameAddressAs(addr) }
+        }
+
     @Test
     fun testCallbacks() {
         // If an interface exists when the callback is registered, it is reported on registration.
@@ -732,4 +761,66 @@
         enableInterface(iface).expectResult(iface.name)
         cb.expectAvailable()
     }
+
+    @Test
+    fun testUpdateConfiguration_forBothIpConfigAndCapabilities() {
+        val iface = createInterface()
+        val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface.name))
+        val network = cb.expectAvailable()
+        cb.assertNeverLost()
+
+        val testCapability = NET_CAPABILITY_TEMPORARILY_NOT_METERED
+        val nc = NetworkCapabilities
+                .Builder(ETH_REQUEST.networkCapabilities)
+                .addCapability(testCapability)
+                .build()
+        updateConfiguration(iface, STATIC_IP_CONFIGURATION, nc)
+
+        // UpdateConfiguration() currently does a restarts on the ethernet interface therefore lost
+        // will be expected first before available, as part of the restart.
+        cb.expectLost(network)
+        cb.expectAvailable()
+        cb.expectCapabilitiesWithCapability(testCapability)
+        cb.expectLinkPropertiesWithLinkAddress(
+                STATIC_IP_CONFIGURATION.staticIpConfiguration.ipAddress!!)
+    }
+
+    @Test
+    fun testUpdateConfiguration_forOnlyIpConfig() {
+        val iface: EthernetTestInterface = createInterface()
+        val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface.name))
+        val network = cb.expectAvailable()
+        cb.assertNeverLost()
+
+        updateConfiguration(iface, STATIC_IP_CONFIGURATION)
+
+        // UpdateConfiguration() currently does a restarts on the ethernet interface therefore lost
+        // will be expected first before available, as part of the restart.
+        cb.expectLost(network)
+        cb.expectAvailable()
+        cb.expectCallback<CapabilitiesChanged>()
+        cb.expectLinkPropertiesWithLinkAddress(
+                STATIC_IP_CONFIGURATION.staticIpConfiguration.ipAddress!!)
+    }
+
+    @Test
+    fun testUpdateConfiguration_forOnlyCapabilities() {
+        val iface: EthernetTestInterface = createInterface()
+        val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface.name))
+        val network = cb.expectAvailable()
+        cb.assertNeverLost()
+
+        val testCapability = NET_CAPABILITY_TEMPORARILY_NOT_METERED
+        val nc = NetworkCapabilities
+                .Builder(ETH_REQUEST.networkCapabilities)
+                .addCapability(testCapability)
+                .build()
+        updateConfiguration(iface, capabilities = nc)
+
+        // UpdateConfiguration() currently does a restarts on the ethernet interface therefore lost
+        // will be expected first before available, as part of the restart.
+        cb.expectLost(network)
+        cb.expectAvailable()
+        cb.expectCapabilitiesWithCapability(testCapability)
+    }
 }