Merge changes I1d0b73ef,I753dac68,Id3fb329b,I7037bee1 into main

* changes:
  Add support for sendPing
  Add support to expect ping reply or dropped ping
  Bind socket to WiFi network
  Add IcmpPacketReader to ApfIntegrationTest
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index 3be44f7..8b66ed0 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -22,33 +22,50 @@
 import android.Manifest.permission.WRITE_DEVICE_CONFIG
 import android.content.pm.PackageManager.FEATURE_WIFI
 import android.net.ConnectivityManager
+import android.net.Network
 import android.net.NetworkCapabilities
 import android.net.NetworkRequest
 import android.net.apf.ApfCapabilities
 import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
 import android.os.PowerManager
 import android.platform.test.annotations.AppModeFull
 import android.provider.DeviceConfig
 import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
+import android.system.Os
 import android.system.OsConstants
+import android.system.OsConstants.AF_INET
+import android.system.OsConstants.IPPROTO_ICMP
+import android.system.OsConstants.SOCK_DGRAM
+import android.system.OsConstants.SOCK_NONBLOCK
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.compatibility.common.util.PropertyUtil.getVsrApiLevel
 import com.android.compatibility.common.util.SystemUtil.runShellCommand
 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
 import com.android.internal.util.HexDump
+import com.android.net.module.util.PacketReader
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.NetworkStackModuleTest
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
 import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
 import com.android.testutils.SkipPresubmit
 import com.android.testutils.TestableNetworkCallback
 import com.android.testutils.runAsShell
+import com.android.testutils.waitForIdle
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import com.google.common.truth.TruthJUnit.assume
+import java.io.FileDescriptor
 import java.lang.Thread
+import java.net.InetSocketAddress
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeUnit
 import kotlin.random.Random
+import kotlin.test.assertFailsWith
 import kotlin.test.assertNotNull
 import org.junit.After
 import org.junit.Before
@@ -61,12 +78,15 @@
 private const val TIMEOUT_MS = 2000L
 private const val APF_NEW_RA_FILTER_VERSION = "apf_new_ra_filter_version"
 private const val POLLING_INTERVAL_MS: Int = 100
+private const val RCV_BUFFER_SIZE = 1480
 
 @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
 @RunWith(DevSdkIgnoreRunner::class)
 @NetworkStackModuleTest
 class ApfIntegrationTest {
     companion object {
+        private val PING_DESTINATION = InetSocketAddress("8.8.8.8", 0)
+
         @BeforeClass
         @JvmStatic
         @Suppress("ktlint:standard:no-multi-spaces")
@@ -86,6 +106,72 @@
         }
     }
 
+    class IcmpPacketReader(
+            handler: Handler,
+            private val network: Network
+    ) : PacketReader(handler, RCV_BUFFER_SIZE) {
+        private var sockFd: FileDescriptor? = null
+        private val futureReply = CompletableFuture<ByteArray>()
+
+        override fun createFd(): FileDescriptor {
+            // sockFd is closed by calling super.stop()
+            val sock = Os.socket(AF_INET, SOCK_DGRAM or SOCK_NONBLOCK, IPPROTO_ICMP)
+            // APF runs only on WiFi, so make sure the socket is bound to the right network.
+            network.bindSocket(sock)
+            sockFd = sock
+            return sock
+        }
+
+        override fun handlePacket(recvbuf: ByteArray, length: Int) {
+            // Only copy the ping data and complete the future.
+            futureReply.complete(recvbuf.sliceArray(8..<length))
+        }
+
+        fun sendPing(data: ByteArray) {
+            require(data.size == 56)
+
+            // rfc792: Echo (type 0x08) or Echo Reply (type 0x00) Message:
+            //  0                   1                   2                   3
+            //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+            // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            // |     Type      |     Code      |          Checksum             |
+            // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            // |           Identifier          |        Sequence Number        |
+            // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            // |     Data ...
+            // +-+-+-+-+-
+            val icmpHeader = byteArrayOf(0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
+            val packet = icmpHeader + data
+            Os.sendto(sockFd!!, packet, 0, packet.size, 0, PING_DESTINATION)
+        }
+
+        fun expectPingReply(): ByteArray {
+            return futureReply.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+        }
+
+        fun expectPingDropped() {
+            assertFailsWith(IllegalStateException::class) {
+                try {
+                    futureReply.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+                } catch (e: ExecutionException) {
+                    throw e.cause!!
+                }
+            }
+        }
+
+        override fun start(): Boolean {
+            // Ignore the fact start() could return false or throw an exception. There is also no
+            // need to wait for start to be processed -- just post it on the handler and return.
+            handler.post({ super.start() })
+            return true
+        }
+
+        override fun stop() {
+            handler.post({ super.stop() })
+            handler.waitForIdle(TIMEOUT_MS)
+        }
+    }
+
     @get:Rule
     val ignoreRule = DevSdkIgnoreRule()
 
@@ -94,9 +180,13 @@
     private val pm by lazy { context.packageManager }
     private val powerManager by lazy { context.getSystemService(PowerManager::class.java)!! }
     private val wakeLock by lazy { powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG) }
+    private lateinit var network: Network
     private lateinit var ifname: String
     private lateinit var networkCallback: TestableNetworkCallback
     private lateinit var caps: ApfCapabilities
+    private val handlerThread = HandlerThread("$TAG handler thread").apply { start() }
+    private val handler = Handler(handlerThread.looper)
+    private lateinit var packetReader: IcmpPacketReader
 
     fun getApfCapabilities(): ApfCapabilities {
         val caps = runShellCommand("cmd network_stack apf $ifname capabilities").trim()
@@ -146,6 +236,7 @@
                         .build(),
                 networkCallback
         )
+        network = networkCallback.expect<Available>().network
         networkCallback.eventuallyExpect<LinkPropertiesChanged>(TIMEOUT_MS) {
             ifname = assertNotNull(it.lp.interfaceName)
             true
@@ -155,10 +246,19 @@
         // respective VSR releases and all other tests are based on the capabilities indicated.
         runShellCommand("cmd network_stack apf $ifname pause")
         caps = getApfCapabilities()
+
+        packetReader = IcmpPacketReader(handler, network)
+        packetReader.start()
     }
 
     @After
     fun tearDown() {
+        if (::packetReader.isInitialized) {
+            packetReader.stop()
+        }
+        handlerThread.quitSafely()
+        handlerThread.join()
+
         if (::ifname.isInitialized) {
             runShellCommand("cmd network_stack apf $ifname resume")
         }