• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 // ktlint does not allow annotating function argument literals inline. Disable the specific rule
17 // since this negatively affects readability.
18 @file:Suppress("ktlint:standard:comment-wrapping")
19 
20 package android.net.cts
21 
22 import android.content.pm.PackageManager.FEATURE_AUTOMOTIVE
23 import android.content.pm.PackageManager.FEATURE_LEANBACK
24 import android.content.pm.PackageManager.FEATURE_WIFI
25 import android.net.ConnectivityManager
26 import android.net.Network
27 import android.net.NetworkCapabilities
28 import android.net.NetworkRequest
29 import android.net.apf.ApfCapabilities
30 import android.net.apf.ApfConstants.ETH_ETHERTYPE_OFFSET
31 import android.net.apf.ApfConstants.ETH_HEADER_LEN
32 import android.net.apf.ApfConstants.ICMP6_CHECKSUM_OFFSET
33 import android.net.apf.ApfConstants.ICMP6_TYPE_OFFSET
34 import android.net.apf.ApfConstants.IPV6_DEST_ADDR_OFFSET
35 import android.net.apf.ApfConstants.IPV6_HEADER_LEN
36 import android.net.apf.ApfConstants.IPV6_NEXT_HEADER_OFFSET
37 import android.net.apf.ApfConstants.IPV6_SRC_ADDR_OFFSET
38 import android.net.apf.ApfCounterTracker
39 import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID
40 import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD
41 import android.net.apf.ApfCounterTracker.Counter.FILTER_AGE_16384THS
42 import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP
43 import android.net.apf.ApfV4Generator
44 import android.net.apf.ApfV4GeneratorBase
45 import android.net.apf.ApfV6Generator
46 import android.net.apf.BaseApfGenerator
47 import android.net.apf.BaseApfGenerator.MemorySlot
48 import android.net.apf.BaseApfGenerator.Register.R0
49 import android.net.apf.BaseApfGenerator.Register.R1
50 import android.os.Build
51 import android.os.Handler
52 import android.os.HandlerThread
53 import android.os.PowerManager
54 import android.os.SystemProperties
55 import android.os.UserManager
56 import android.platform.test.annotations.AppModeFull
57 import android.system.Os
58 import android.system.OsConstants
59 import android.system.OsConstants.AF_INET6
60 import android.system.OsConstants.ETH_P_IPV6
61 import android.system.OsConstants.ICMP6_ECHO_REPLY
62 import android.system.OsConstants.ICMP6_ECHO_REQUEST
63 import android.system.OsConstants.IPPROTO_ICMPV6
64 import android.system.OsConstants.SOCK_DGRAM
65 import android.system.OsConstants.SOCK_NONBLOCK
66 import android.util.Log
67 import androidx.test.filters.RequiresDevice
68 import androidx.test.platform.app.InstrumentationRegistry
69 import com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel
70 import com.android.compatibility.common.util.PropertyUtil.getVsrApiLevel
71 import com.android.compatibility.common.util.SystemUtil.runShellCommand
72 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
73 import com.android.compatibility.common.util.VsrTest
74 import com.android.internal.util.HexDump
75 import com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN
76 import com.android.net.module.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET
77 import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN
78 import com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET
79 import com.android.net.module.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN
80 import com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN
81 import com.android.net.module.util.PacketReader
82 import com.android.testutils.DevSdkIgnoreRule
83 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
84 import com.android.testutils.DevSdkIgnoreRunner
85 import com.android.testutils.NetworkStackModuleTest
86 import com.android.testutils.RecorderCallback.CallbackEntry.Available
87 import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
88 import com.android.testutils.SkipPresubmit
89 import com.android.testutils.TestableNetworkCallback
90 import com.android.testutils.pollingCheck
91 import com.android.testutils.waitForIdle
92 import com.google.common.truth.Expect
93 import com.google.common.truth.Truth.assertThat
94 import com.google.common.truth.Truth.assertWithMessage
95 import com.google.common.truth.TruthJUnit.assume
96 import java.io.FileDescriptor
97 import java.net.InetSocketAddress
98 import java.nio.ByteBuffer
99 import java.util.concurrent.CompletableFuture
100 import java.util.concurrent.TimeUnit
101 import java.util.concurrent.TimeoutException
102 import kotlin.random.Random
103 import kotlin.test.assertEquals
104 import kotlin.test.assertFailsWith
105 import kotlin.test.assertNotNull
106 import org.junit.After
107 import org.junit.AfterClass
108 import org.junit.Assume.assumeFalse
109 import org.junit.Before
110 import org.junit.BeforeClass
111 import org.junit.Rule
112 import org.junit.Test
113 import org.junit.runner.RunWith
114 
115 private const val TAG = "ApfIntegrationTest"
116 private const val TIMEOUT_MS = 2000L
117 private const val RCV_BUFFER_SIZE = 1480
118 private const val PING_HEADER_LENGTH = 8
119 
120 @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
121 @RunWith(DevSdkIgnoreRunner::class)
122 @RequiresDevice
123 @NetworkStackModuleTest
124 // ByteArray.toHexString is experimental API
125 @kotlin.ExperimentalStdlibApi
126 class ApfIntegrationTest {
127     companion object {
128         private val PING_DESTINATION = InetSocketAddress("2001:4860:4860::8888", 0)
129 
130         private val context = InstrumentationRegistry.getInstrumentation().context
131         private val powerManager = context.getSystemService(PowerManager::class.java)!!
132         private val wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG)
133 
134         fun turnScreenOff() {
135             if (!wakeLock.isHeld()) wakeLock.acquire()
136             runShellCommandOrThrow("input keyevent KEYCODE_SLEEP")
137             waitForInteractiveState(false)
138         }
139 
140         fun turnScreenOn() {
141             if (wakeLock.isHeld()) wakeLock.release()
142             runShellCommandOrThrow("input keyevent KEYCODE_WAKEUP")
143             waitForInteractiveState(true)
144         }
145 
146         private fun waitForInteractiveState(interactive: Boolean) {
147             // TODO(b/366037029): This test condition should be removed once
148             // PowerManager#isInteractive is fully implemented on automotive
149             // form factor with visible background user.
150             if (isAutomotiveWithVisibleBackgroundUser()) {
151                 // Wait for 2 seconds to ensure the interactive state is updated.
152                 // This is a workaround for b/366037029.
153                 Thread.sleep(2000L)
154             } else {
155                 val result = pollingCheck(timeout_ms = 2000) {
156                     powerManager.isInteractive()
157                 }
158                 assertThat(result).isEqualTo(interactive)
159             }
160         }
161 
162         private fun isAutomotiveWithVisibleBackgroundUser(): Boolean {
163             val packageManager = context.getPackageManager()
164             val userManager = context.getSystemService(UserManager::class.java)!!
165             return (packageManager.hasSystemFeature(FEATURE_AUTOMOTIVE) &&
166                     userManager.isVisibleBackgroundUsersSupported)
167         }
168 
169         @BeforeClass
170         @JvmStatic
171         @Suppress("ktlint:standard:no-multi-spaces")
172         fun setupOnce() {
173             // TODO: assertions thrown in @BeforeClass / @AfterClass are not well supported in the
174             // test infrastructure. Consider saving exception and throwing it in setUp().
175 
176             // APF must run when the screen is off and the device is not interactive.
177             turnScreenOff()
178 
179             // Wait for APF to become active.
180             Thread.sleep(1000)
181             // TODO: check that there is no active wifi network. Otherwise, ApfFilter has already been
182             // created.
183         }
184 
185         @AfterClass
186         @JvmStatic
187         fun tearDownOnce() {
188             turnScreenOn()
189         }
190     }
191 
192     class Icmp6PacketReader(
193             handler: Handler,
194             private val network: Network
195     ) : PacketReader(handler, RCV_BUFFER_SIZE) {
196         private data class PingContext(
197             val futureReply: CompletableFuture<List<ByteArray>>,
198             val expectReplyCount: Int,
199             val replyPayloads: MutableList<ByteArray> = mutableListOf()
200         )
201         private var sockFd: FileDescriptor? = null
202         private var pingContext: PingContext? = null
203 
204         override fun createFd(): FileDescriptor {
205             // sockFd is closed by calling super.stop()
206             val sock = Os.socket(AF_INET6, SOCK_DGRAM or SOCK_NONBLOCK, IPPROTO_ICMPV6)
207             // APF runs only on WiFi, so make sure the socket is bound to the right network.
208             network.bindSocket(sock)
209             sockFd = sock
210             return sock
211         }
212 
213         override fun handlePacket(recvbuf: ByteArray, length: Int) {
214             val context = pingContext ?: return
215 
216             // If zero-length or Type is not echo reply: ignore.
217             if (length == 0 || recvbuf[0] != 0x81.toByte()) {
218                 return
219             }
220             // Only copy the ping data and complete the future.
221             val result = recvbuf.sliceArray(8..<length)
222             Log.i(TAG, "Received ping reply: ${result.toHexString()}")
223             context.replyPayloads.add(recvbuf.sliceArray(8..<length))
224             if (context.replyPayloads.size == context.expectReplyCount) {
225                 context.futureReply.complete(context.replyPayloads)
226                 pingContext = null
227             }
228         }
229 
230         fun sendPing(data: ByteArray, payloadSize: Int, expectReplyCount: Int = 1) {
231             require(data.size == payloadSize)
232 
233             // rfc4443#section-4.1: Echo Request Message
234             //   0                   1                   2                   3
235             //   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
236             //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
237             //  |     Type      |     Code      |          Checksum             |
238             //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
239             //  |           Identifier          |        Sequence Number        |
240             //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
241             //  |     Data ...
242             //  +-+-+-+-+-
243             val icmp6Header = byteArrayOf(0x80.toByte(), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
244             val packet = icmp6Header + data
245             Log.i(TAG, "Sent ping: ${packet.toHexString()}")
246             pingContext = PingContext(
247                 futureReply = CompletableFuture<List<ByteArray>>(),
248                 expectReplyCount = expectReplyCount
249             )
250             Os.sendto(sockFd!!, packet, 0, packet.size, 0, PING_DESTINATION)
251         }
252 
253         fun expectPingReply(timeoutMs: Long = TIMEOUT_MS): List<ByteArray> {
254             return pingContext!!.futureReply.get(timeoutMs, TimeUnit.MILLISECONDS)
255         }
256 
257         fun expectPingDropped() {
258             assertFailsWith(TimeoutException::class) {
259                 pingContext!!.futureReply.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
260             }
261         }
262 
263         override fun start(): Boolean {
264             // Ignore the fact start() could return false or throw an exception.
265             handler.post({ super.start() })
266             handler.waitForIdle(TIMEOUT_MS)
267             return true
268         }
269 
270         override fun stop() {
271             handler.post({ super.stop() })
272             handler.waitForIdle(TIMEOUT_MS)
273         }
274     }
275 
276     @get:Rule val ignoreRule = DevSdkIgnoreRule()
277     @get:Rule val expect = Expect.create()
278 
279     private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! }
280     private val pm by lazy { context.packageManager }
281     private lateinit var network: Network
282     private lateinit var ifname: String
283     private lateinit var networkCallback: TestableNetworkCallback
284     private lateinit var caps: ApfCapabilities
285     private val handlerThread = HandlerThread("$TAG handler thread").apply { start() }
286     private val handler = Handler(handlerThread.looper)
287     private lateinit var packetReader: Icmp6PacketReader
288 
289     fun getApfCapabilities(): ApfCapabilities {
290         val caps = runShellCommand("cmd network_stack apf $ifname capabilities").trim()
291         if (caps.isEmpty()) {
292             return ApfCapabilities(0, 0, 0)
293         }
294         val (version, maxLen, packetFormat) = caps.split(",").map { it.toInt() }
295         return ApfCapabilities(version, maxLen, packetFormat)
296     }
297 
298     private fun isTvDeviceSupportFullNetworkingUnder2w(): Boolean {
299         return (pm.hasSystemFeature(FEATURE_LEANBACK) &&
300             pm.hasSystemFeature("com.google.android.tv.full_networking_under_2w"))
301     }
302 
303     @Before
304     fun setUp() {
305         assume().that(pm.hasSystemFeature(FEATURE_WIFI)).isTrue()
306 
307         // Based on GTVS-16, Android Packet Filtering (APF) is OPTIONAL for devices that fully
308         // process all network packets on CPU at all times, even in standby, while meeting
309         // the <= 2W standby power demand requirement.
310         assumeFalse(
311             "Skipping test: TV device process full networking on CPU under 2W",
312             isTvDeviceSupportFullNetworkingUnder2w()
313         )
314 
315         networkCallback = TestableNetworkCallback()
316         cm.requestNetwork(
317                 NetworkRequest.Builder()
318                         .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
319                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
320                         .build(),
321                 networkCallback
322         )
323         network = networkCallback.expect<Available>().network
324         networkCallback.eventuallyExpect<LinkPropertiesChanged>(TIMEOUT_MS) {
325             ifname = assertNotNull(it.lp.interfaceName)
326             true
327         }
328         // It's possible the device does not support APF, in which case this command will not be
329         // successful. Ignore the error as testApfCapabilities() already asserts APF support on the
330         // respective VSR releases and all other tests are based on the capabilities indicated.
331         runShellCommand("cmd network_stack apf $ifname pause")
332         caps = getApfCapabilities()
333 
334         packetReader = Icmp6PacketReader(handler, network)
335         packetReader.start()
336     }
337 
338     @After
339     fun tearDown() {
340         if (::packetReader.isInitialized) {
341             packetReader.stop()
342         }
343         handlerThread.quitSafely()
344         handlerThread.join()
345 
346         if (::ifname.isInitialized) {
347             runShellCommand("cmd network_stack apf $ifname resume")
348         }
349         if (::networkCallback.isInitialized) {
350             cm.unregisterNetworkCallback(networkCallback)
351         }
352     }
353 
354     @VsrTest(
355         requirements = ["VSR-5.3.12-001", "VSR-5.3.12-003", "VSR-5.3.12-004", "VSR-5.3.12-009",
356             "VSR-5.3.12-012"]
357     )
358     @Test
359     fun testApfCapabilities() {
360         // APF became mandatory in Android 14 VSR.
361         val vsrApiLevel = getVsrApiLevel()
362         assume().that(vsrApiLevel).isAtLeast(34)
363 
364         // DEVICEs launching with Android 14 with CHIPSETs that set ro.board.first_api_level to 34:
365         // - [GMS-VSR-5.3.12-003] MUST return 4 or higher as the APF version number from calls to
366         //   the getApfPacketFilterCapabilities HAL method.
367         // - [GMS-VSR-5.3.12-004] MUST indicate at least 1024 bytes of usable memory from calls to
368         //   the getApfPacketFilterCapabilities HAL method.
369         // TODO: check whether above text should be changed "34 or higher"
370         assertThat(caps.apfVersionSupported).isAtLeast(4)
371         assertThat(caps.maximumApfProgramSize).isAtLeast(1024)
372 
373         if (caps.apfVersionSupported > 4) {
374             assertThat(caps.maximumApfProgramSize).isAtLeast(2048)
375             assertThat(caps.apfVersionSupported).isAnyOf(6000, 6100) // v6.000 or v6.100
376         }
377 
378         // DEVICEs launching with Android 15 (AOSP experimental) or higher with CHIPSETs that set
379         // ro.board.first_api_level or ro.board.api_level to 202404 or higher:
380         // - [GMS-VSR-5.3.12-009] MUST indicate at least 2048 bytes of usable memory from calls to
381         //   the getApfPacketFilterCapabilities HAL method.
382         if (vsrApiLevel >= 202404) {
383             assertThat(caps.maximumApfProgramSize).isAtLeast(2048)
384         }
385 
386         // DEVICEs with CHIPSETs that set ro.board.first_api_level or ro.board.api_level to 202504
387         // or higher:
388         // - [VSR-5.3.12-018] MUST implement version 6 or version 6.1 of the Android Packet
389         //   Filtering (APF) interpreter in the Wi-Fi firmware.
390         // - [VSR-5.3.12-019] MUST provide at least 4000 bytes of APF RAM when version 6 is
391         //   implemented OR 3000 bytes when version 6.1 is implemented.
392         // - Note, the APF RAM requirement for APF version 6.1 will become 4000 bytes in Android 17
393         //   with CHIPSETs that set ro.board.first_api_level or ro.board.api_level to 202604 or
394         //   higher.
395         if (vsrApiLevel >= 202504) {
396             assertThat(caps.apfVersionSupported).isAnyOf(6000, 6100)
397             if (caps.apfVersionSupported == 6000) {
398                 assertThat(caps.maximumApfProgramSize).isAtLeast(4000)
399             } else {
400                 assertThat(caps.maximumApfProgramSize).isAtLeast(3000)
401             }
402         }
403 
404         // ApfFilter does not support anything but ARPHRD_ETHER.
405         assertThat(caps.apfPacketFormat).isEqualTo(OsConstants.ARPHRD_ETHER)
406     }
407 
408     // APF is backwards compatible, i.e. a v6 interpreter supports both v2 and v4 functionality.
409     fun assumeApfVersionSupportAtLeast(version: Int) {
410         assume().that(caps.apfVersionSupported).isAtLeast(version)
411     }
412 
413     fun assumeNotCuttlefish() {
414         assume().that(SystemProperties.get("ro.product.board", "")).isNotEqualTo("cutf")
415     }
416 
417     fun installProgram(bytes: ByteArray) {
418         val prog = bytes.toHexString()
419         val result = runShellCommandOrThrow("cmd network_stack apf $ifname install $prog").trim()
420         // runShellCommandOrThrow only throws on S+.
421         assertThat(result).isEqualTo("success")
422     }
423 
424     fun readProgram(): ByteArray {
425         val progHexString = runShellCommandOrThrow("cmd network_stack apf $ifname read").trim()
426         // runShellCommandOrThrow only throws on S+.
427         assertThat(progHexString).isNotEmpty()
428         return HexDump.hexStringToByteArray(progHexString)
429     }
430 
431     @VsrTest(
432             requirements = ["VSR-5.3.12-007", "VSR-5.3.12-008", "VSR-5.3.12-010", "VSR-5.3.12-011"]
433     )
434     @SkipPresubmit(reason = "This test takes longer than 1 minute, do not run it on presubmit.")
435     // APF integration is mostly broken before V, only run the full read / write test on V+.
436     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
437     // Increase timeout for test to 15 minutes to accommodate device with large APF RAM.
438     @Test(timeout = 15 * 60 * 1000)
439     fun testReadWriteProgram() {
440         assumeApfVersionSupportAtLeast(4)
441 
442         val minReadWriteSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
443             2
444         } else {
445             8
446         }
447 
448         // The minReadWriteSize is 2 bytes. The first byte always stays PASS.
449         val program = ByteArray(caps.maximumApfProgramSize)
450         for (i in caps.maximumApfProgramSize downTo minReadWriteSize) {
451             // Randomize bytes in range [1, i). And install first [0, i) bytes of program.
452             // Note that only the very first instruction (PASS) is valid APF bytecode.
453             Random.nextBytes(program, 1 /* fromIndex */, i /* toIndex */)
454             installProgram(program.sliceArray(0..<i))
455 
456             // Compare entire memory region.
457             val readResult = readProgram()
458             val errMsg = """
459                 read/write $i byte prog failed.
460                 In APFv4, the APF memory region MUST NOT be modified or cleared except by APF
461                 instructions executed by the interpreter or by Android OS calls to the HAL. If this
462                 requirement cannot be met, the firmware cannot declare that it supports APFv4 and
463                 it should declare that it only supports APFv3(if counter is partially supported) or
464                 APFv2.
465             """.trimIndent()
466             assertWithMessage(errMsg).that(readResult).isEqualTo(program)
467         }
468     }
469 
470     private fun installAndVerifyProgram(program: ByteArray) {
471         installProgram(program)
472         val readResult = readProgram().take(program.size).toByteArray()
473         assertThat(readResult).isEqualTo(program)
474     }
475 
476     fun ApfV4GeneratorBase<*>.addPassIfNotIcmpv6EchoReply(skipPacketLabel: Short) {
477         // If not IPv6 -> PASS
478         addLoad16intoR0(ETH_ETHERTYPE_OFFSET)
479         addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), skipPacketLabel)
480 
481         // If not ICMPv6 -> PASS
482         addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET)
483         addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), skipPacketLabel)
484 
485         // If not echo reply -> PASS
486         addLoad8intoR0(ICMP6_TYPE_OFFSET)
487         addJumpIfR0NotEquals(0x81, skipPacketLabel)
488     }
489 
490     // APF integration is mostly broken before V
491     @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
492     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
493     @Test
494     fun testDropPingReply() {
495         // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link
496         // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF
497         // should be turned on.
498         assume().that(getVsrApiLevel()).isAtLeast(34)
499         assumeApfVersionSupportAtLeast(4)
500         assumeNotCuttlefish()
501 
502         // clear any active APF filter
503         clearApfMemory()
504         readProgram() // wait for install completion
505 
506         // Assert that initial ping does not get filtered.
507         val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
508             68
509         } else {
510             4
511         }
512         val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
513         packetReader.sendPing(data, payloadSize)
514         assertThat(packetReader.expectPingReply()[0]).isEqualTo(data)
515 
516         // Generate an APF program that drops the next ping
517         val gen = ApfV4Generator(
518                 caps.apfVersionSupported,
519                 caps.maximumApfProgramSize,
520                 caps.maximumApfProgramSize
521         )
522 
523         val skipPacketLabel = gen.uniqueLabel
524         // If not ICMPv6 Echo Reply -> PASS
525         gen.addPassIfNotIcmpv6EchoReply(skipPacketLabel)
526 
527         // if not data matches -> PASS
528         gen.addLoadImmediate(R0, ICMP6_TYPE_OFFSET + PING_HEADER_LENGTH)
529         gen.addJumpIfBytesAtR0NotEqual(data, skipPacketLabel)
530 
531         // else DROP
532         // Warning: the program abuse DROPPED_IPV6_NS_INVALID/PASSED_IPV6_ICMP for debugging purpose
533         gen.addCountAndDrop(DROPPED_IPV6_NS_INVALID)
534             .defineLabel(skipPacketLabel)
535             .addCountAndPass(PASSED_IPV6_ICMP)
536             .addCountTrampoline()
537 
538         val program = gen.generate()
539         installAndVerifyProgram(program)
540 
541         val counterBefore = ApfCounterTracker.getCounterValue(
542             readProgram(),
543             DROPPED_IPV6_NS_INVALID
544         )
545         packetReader.sendPing(data, payloadSize)
546         packetReader.expectPingDropped()
547         val counterAfter = ApfCounterTracker.getCounterValue(
548             readProgram(),
549             DROPPED_IPV6_NS_INVALID
550         )
551         assertEquals(counterBefore + 1, counterAfter)
552     }
553 
554     fun clearApfMemory() = installProgram(ByteArray(caps.maximumApfProgramSize))
555 
556     // APF integration is mostly broken before V
557     @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
558     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
559     @Test
560     fun testPrefilledMemorySlotsV4() {
561         // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link
562         // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF
563         // should be turned on.
564         assume().that(getVsrApiLevel()).isAtLeast(34)
565         // Test v4 memory slots on both v4 and v6 interpreters.
566         assumeApfVersionSupportAtLeast(4)
567         assumeNotCuttlefish()
568         clearApfMemory()
569         val gen = ApfV4Generator(
570                 caps.apfVersionSupported,
571                 caps.maximumApfProgramSize,
572                 caps.maximumApfProgramSize
573         )
574 
575         // If not ICMPv6 Echo Reply -> PASS
576         gen.addPassIfNotIcmpv6EchoReply(BaseApfGenerator.PASS_LABEL)
577 
578         // Store all prefilled memory slots in counter region [500, 520)
579         val counterRegion = 500
580         gen.addLoadImmediate(R1, counterRegion)
581         gen.addLoadFromMemory(R0, MemorySlot.PROGRAM_SIZE)
582         gen.addStoreData(R0, 0)
583         gen.addLoadFromMemory(R0, MemorySlot.RAM_LEN)
584         gen.addStoreData(R0, 4)
585         gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE)
586         gen.addStoreData(R0, 8)
587         gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
588         gen.addStoreData(R0, 12)
589         gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS)
590         gen.addStoreData(R0, 16)
591 
592         val program = gen.generate()
593         assertThat(program.size).isLessThan(counterRegion)
594         val randomProgram = ByteArray(1) { 0 } +
595                 ByteArray(counterRegion - 1).also { Random.nextBytes(it) }
596         // There are known firmware bugs where they calculate the number of non-zero bytes within
597         // the program to determine the program length. Modify the test to first install a longer
598         // program before installing a program that do the program length check. This should help us
599         // catch these types of firmware bugs in CTS. (b/395545572)
600         installAndVerifyProgram(randomProgram)
601         installAndVerifyProgram(program)
602 
603         // Trigger the program by sending a ping and waiting on the reply.
604         val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
605             68
606         } else {
607             4
608         }
609         val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
610         packetReader.sendPing(data, payloadSize)
611         packetReader.expectPingReply()
612 
613         val readResult = readProgram()
614         val buffer = ByteBuffer.wrap(readResult, counterRegion, 20 /* length */)
615         expect.withMessage("PROGRAM_SIZE").that(buffer.getInt()).isEqualTo(program.size)
616         expect.withMessage("RAM_LEN").that(buffer.getInt()).isEqualTo(caps.maximumApfProgramSize)
617         expect.withMessage("IPV4_HEADER_SIZE").that(buffer.getInt()).isEqualTo(0)
618         // Ping packet payload + ICMPv6 header (8)  + IPv6 header (40) + ethernet header (14)
619         expect.withMessage("PACKET_SIZE").that(buffer.getInt()).isEqualTo(payloadSize + 8 + 40 + 14)
620         expect.withMessage("FILTER_AGE_SECONDS").that(buffer.getInt()).isLessThan(5)
621     }
622 
623     // APF integration is mostly broken before V
624     @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
625     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
626     @Test
627     fun testFilterAgeIncreasesBetweenPackets() {
628         // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link
629         // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF
630         // should be turned on.
631         assume().that(getVsrApiLevel()).isAtLeast(34)
632         assumeApfVersionSupportAtLeast(4)
633         assumeNotCuttlefish()
634         clearApfMemory()
635         val gen = ApfV4Generator(
636                 caps.apfVersionSupported,
637                 caps.maximumApfProgramSize,
638                 caps.maximumApfProgramSize
639         )
640 
641         // If not ICMPv6 Echo Reply -> PASS
642         gen.addPassIfNotIcmpv6EchoReply(BaseApfGenerator.PASS_LABEL)
643 
644         // Store all prefilled memory slots in counter region [500, 520)
645         val counterRegion = 500
646         gen.addLoadImmediate(R1, counterRegion)
647         gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS)
648         gen.addStoreData(R0, 0)
649 
650         installAndVerifyProgram(gen.generate())
651 
652         val payloadSize = 56
653         val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
654         packetReader.sendPing(data, payloadSize)
655         packetReader.expectPingReply()
656 
657         var buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */)
658         val filterAgeSecondsOrig = buffer.getInt()
659 
660         Thread.sleep(5100)
661 
662         packetReader.sendPing(data, payloadSize)
663         packetReader.expectPingReply()
664 
665         buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */)
666         val filterAgeSeconds = buffer.getInt()
667         // Assert that filter age has increased, but not too much.
668         val timeDiff = filterAgeSeconds - filterAgeSecondsOrig
669         assertThat(timeDiff).isAnyOf(5, 6)
670     }
671 
672     @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
673     @Test
674     fun testFilterAge16384thsIncreasesBetweenPackets() {
675         assumeApfVersionSupportAtLeast(6000)
676         assumeNotCuttlefish()
677         clearApfMemory()
678         val gen = ApfV6Generator(
679                 caps.apfVersionSupported,
680                 caps.maximumApfProgramSize,
681                 caps.maximumApfProgramSize
682         )
683 
684         // If not ICMPv6 Echo Reply -> PASS
685         gen.addPassIfNotIcmpv6EchoReply(BaseApfGenerator.PASS_LABEL)
686 
687         // Store all prefilled memory slots in counter region [500, 520)
688         gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_16384THS)
689         gen.addStoreCounter(FILTER_AGE_16384THS, R0)
690 
691         installAndVerifyProgram(gen.generate())
692 
693         val payloadSize = 56
694         val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
695         packetReader.sendPing(data, payloadSize)
696         packetReader.expectPingReply()
697 
698         var apfRam = readProgram()
699         val filterAge16384thSecondsOrig =
700                 ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS)
701 
702         Thread.sleep(5000)
703 
704         packetReader.sendPing(data, payloadSize)
705         packetReader.expectPingReply()
706 
707         apfRam = readProgram()
708         val filterAge16384thSeconds = ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS)
709         val timeDiff = (filterAge16384thSeconds - filterAge16384thSecondsOrig)
710         // Expect the HAL plus ping latency to be less than 800ms.
711         val timeDiffLowerBound = (4.99 * 16384).toInt()
712         val timeDiffUpperBound = (5.81 * 16384).toInt()
713         // Assert that filter age has increased, but not too much.
714         assertThat(timeDiff).isGreaterThan(timeDiffLowerBound)
715         assertThat(timeDiff).isLessThan(timeDiffUpperBound)
716     }
717 
718     @VsrTest(
719             requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005", "VSR-5.3.12-012", "VSR-5.3.12-013",
720                 "VSR-5.3.12-014", "VSR-5.3.12-015", "VSR-5.3.12-016", "VSR-5.3.12-017"]
721     )
722     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
723     @Test
724     fun testReplyPing() {
725         assumeApfVersionSupportAtLeast(6000)
726         assumeNotCuttlefish()
727         installProgram(ByteArray(caps.maximumApfProgramSize) { 0 }) // Clear previous program
728         readProgram() // Ensure installation is complete
729 
730         val payloadSize = 56
731         val payload = ByteArray(payloadSize).also { Random.nextBytes(it) }
732         val firstByte = payload.take(1).toByteArray()
733 
734         val pingRequestIpv6PayloadLen = PING_HEADER_LENGTH + 1
735         val pingRequestPktLen = ETH_HEADER_LEN + IPV6_HEADER_LEN + pingRequestIpv6PayloadLen
736 
737         val gen = ApfV6Generator(
738                 caps.apfVersionSupported,
739                 caps.maximumApfProgramSize,
740                 caps.maximumApfProgramSize
741         )
742         val skipPacketLabel = gen.uniqueLabel
743 
744         // Summary of the program:
745         //   if the packet is not ICMPv6 echo reply
746         //     pass
747         //   else if the echo reply payload size is 1
748         //     increase PASSED_IPV6_ICMP counter
749         //     pass
750         //   else
751         //     transmit 3 ICMPv6 echo requests with random first byte
752         //     increase DROPPED_IPV6_NS_REPLIED_NON_DAD counter
753         //     drop
754         gen.addLoad16intoR0(ETH_ETHERTYPE_OFFSET)
755                 .addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), skipPacketLabel)
756                 .addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET)
757                 .addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), skipPacketLabel)
758                 .addLoad8intoR0(ICMP6_TYPE_OFFSET)
759                 .addJumpIfR0NotEquals(ICMP6_ECHO_REPLY.toLong(), skipPacketLabel)
760                 .addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
761                 .addCountAndPassIfR0Equals(
762                     (ETHER_HEADER_LEN + IPV6_HEADER_LEN + PING_HEADER_LENGTH + firstByte.size)
763                         .toLong(),
764                     PASSED_IPV6_ICMP
765                 )
766 
767         val numOfPacketToTransmit = 3
768         val expectReplyPayloads = (0 until numOfPacketToTransmit).map { Random.nextBytes(1) }
769         expectReplyPayloads.forEach { replyPingPayload ->
770             // Ping Packet Generation
771             gen.addAllocate(pingRequestPktLen)
772                     // Eth header
773                     .addPacketCopy(ETHER_SRC_ADDR_OFFSET, ETHER_ADDR_LEN) // dst MAC address
774                     .addPacketCopy(ETHER_DST_ADDR_OFFSET, ETHER_ADDR_LEN) // src MAC address
775                     .addWriteU16(ETH_P_IPV6) // IPv6 type
776                     // IPv6 Header
777                     .addWrite32(0x60000000) // IPv6 Header: version, traffic class, flowlabel
778                     // payload length (2 bytes) | next header: ICMPv6 (1 byte) | hop limit (1 byte)
779                     .addWrite32(pingRequestIpv6PayloadLen shl 16 or (IPPROTO_ICMPV6 shl 8 or 64))
780                     .addPacketCopy(IPV6_DEST_ADDR_OFFSET, IPV6_ADDR_LEN) // src ip
781                     .addPacketCopy(IPV6_SRC_ADDR_OFFSET, IPV6_ADDR_LEN) // dst ip
782                     // ICMPv6
783                     .addWriteU8(ICMP6_ECHO_REQUEST)
784                     .addWriteU8(0) // code
785                     .addWriteU16(pingRequestIpv6PayloadLen) // checksum
786                     // identifier
787                     .addPacketCopy(ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_HEADER_MIN_LEN, 2)
788                     .addWriteU16(0) // sequence number
789                     .addDataCopy(replyPingPayload) // data
790                     .addTransmitL4(
791                         ETHER_HEADER_LEN, // ip_ofs
792                         ICMP6_CHECKSUM_OFFSET, // csum_ofs
793                         IPV6_SRC_ADDR_OFFSET, // csum_start
794                         IPPROTO_ICMPV6, // partial_sum
795                         false // udp
796                     )
797         }
798 
799         // Warning: the program abuse DROPPED_IPV6_NS_REPLIED_NON_DAD for debugging purpose
800         gen.addCountAndDrop(DROPPED_IPV6_NS_REPLIED_NON_DAD)
801             .defineLabel(skipPacketLabel)
802             .addPass()
803 
804         val program = gen.generate()
805         installAndVerifyProgram(program)
806 
807         val counterBefore = ApfCounterTracker.getCounterValue(
808             readProgram(),
809             DROPPED_IPV6_NS_REPLIED_NON_DAD
810         )
811         packetReader.sendPing(payload, payloadSize, expectReplyCount = numOfPacketToTransmit)
812         val replyPayloads = try {
813             packetReader.expectPingReply(TIMEOUT_MS * 2)
814         } catch (e: TimeoutException) {
815             emptyList()
816         }
817 
818         val apfCounterTracker = ApfCounterTracker()
819         val apfRam = readProgram()
820         apfCounterTracker.updateCountersFromData(apfRam)
821         Log.i(TAG, "counter map: ${apfCounterTracker.counters}")
822 
823         val counterAfter = ApfCounterTracker.getCounterValue(
824             apfRam,
825             DROPPED_IPV6_NS_REPLIED_NON_DAD
826         )
827         assertEquals(counterBefore + 1, counterAfter)
828 
829         assertThat(replyPayloads.size).isEqualTo(expectReplyPayloads.size)
830 
831         // Sort the payload list before comparison to ensure consistency.
832         val sortedReplyPayloads = replyPayloads.sortedBy { it[0] }
833         val sortedExpectReplyPayloads = expectReplyPayloads.sortedBy { it[0] }
834         for (i in sortedReplyPayloads.indices) {
835             assertThat(sortedReplyPayloads[i]).isEqualTo(sortedExpectReplyPayloads[i])
836         }
837     }
838 }
839