1 /*
2  * 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.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG
23 import android.Manifest.permission.WRITE_DEVICE_CONFIG
24 import android.content.pm.PackageManager
25 import android.content.pm.PackageManager.FEATURE_AUTOMOTIVE
26 import android.content.pm.PackageManager.FEATURE_WIFI
27 import android.net.ConnectivityManager
28 import android.net.Network
29 import android.net.NetworkCapabilities
30 import android.net.NetworkRequest
31 import android.net.apf.ApfCapabilities
32 import android.net.apf.ApfConstants.ETH_ETHERTYPE_OFFSET
33 import android.net.apf.ApfConstants.ETH_HEADER_LEN
34 import android.net.apf.ApfConstants.ICMP6_CHECKSUM_OFFSET
35 import android.net.apf.ApfConstants.ICMP6_TYPE_OFFSET
36 import android.net.apf.ApfConstants.IPV6_DEST_ADDR_OFFSET
37 import android.net.apf.ApfConstants.IPV6_HEADER_LEN
38 import android.net.apf.ApfConstants.IPV6_NEXT_HEADER_OFFSET
39 import android.net.apf.ApfConstants.IPV6_SRC_ADDR_OFFSET
40 import android.net.apf.ApfCounterTracker
41 import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_PING
42 import android.net.apf.ApfCounterTracker.Counter.FILTER_AGE_16384THS
43 import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP
44 import android.net.apf.ApfV4Generator
45 import android.net.apf.ApfV4GeneratorBase
46 import android.net.apf.ApfV6Generator
47 import android.net.apf.BaseApfGenerator
48 import android.net.apf.BaseApfGenerator.MemorySlot
49 import android.net.apf.BaseApfGenerator.Register.R0
50 import android.net.apf.BaseApfGenerator.Register.R1
51 import android.os.Build
52 import android.os.Handler
53 import android.os.HandlerThread
54 import android.os.PowerManager
55 import android.os.UserManager
56 import android.platform.test.annotations.AppModeFull
57 import android.provider.DeviceConfig
58 import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
59 import android.system.Os
60 import android.system.OsConstants
61 import android.system.OsConstants.AF_INET6
62 import android.system.OsConstants.ETH_P_IPV6
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.runAsShell
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.assertFailsWith
104 import kotlin.test.assertNotNull
105 import org.junit.After
106 import org.junit.AfterClass
107 import org.junit.Before
108 import org.junit.BeforeClass
109 import org.junit.Rule
110 import org.junit.Test
111 import org.junit.runner.RunWith
112 
113 private const val TAG = "ApfIntegrationTest"
114 private const val TIMEOUT_MS = 2000L
115 private const val APF_NEW_RA_FILTER_VERSION = "apf_new_ra_filter_version"
116 private const val POLLING_INTERVAL_MS: Int = 100
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 
pollingChecknull134         fun pollingCheck(condition: () -> Boolean, timeout_ms: Int): Boolean {
135             var polling_time = 0
136             do {
137                 Thread.sleep(POLLING_INTERVAL_MS.toLong())
138                 polling_time += POLLING_INTERVAL_MS
139                 if (condition()) return true
140             } while (polling_time < timeout_ms)
141             return false
142         }
143 
turnScreenOffnull144         fun turnScreenOff() {
145             if (!wakeLock.isHeld()) wakeLock.acquire()
146             runShellCommandOrThrow("input keyevent KEYCODE_SLEEP")
147             waitForInteractiveState(false)
148         }
149 
turnScreenOnnull150         fun turnScreenOn() {
151             if (wakeLock.isHeld()) wakeLock.release()
152             runShellCommandOrThrow("input keyevent KEYCODE_WAKEUP")
153             waitForInteractiveState(true)
154         }
155 
waitForInteractiveStatenull156         private fun waitForInteractiveState(interactive: Boolean) {
157             // TODO(b/366037029): This test condition should be removed once
158             // PowerManager#isInteractive is fully implemented on automotive
159             // form factor with visible background user.
160             if (isAutomotiveWithVisibleBackgroundUser()) {
161                 // Wait for 2 seconds to ensure the interactive state is updated.
162                 // This is a workaround for b/366037029.
163                 Thread.sleep(2000L)
164             } else {
165                 val result = pollingCheck({ powerManager.isInteractive() }, timeout_ms = 2000)
166                 assertThat(result).isEqualTo(interactive)
167             }
168         }
169 
isAutomotiveWithVisibleBackgroundUsernull170         private fun isAutomotiveWithVisibleBackgroundUser(): Boolean {
171             val packageManager = context.getPackageManager()
172             val userManager = context.getSystemService(UserManager::class.java)!!
173             return (packageManager.hasSystemFeature(FEATURE_AUTOMOTIVE)
174                     && userManager.isVisibleBackgroundUsersSupported)
175         }
176 
177         @BeforeClass
178         @JvmStatic
179         @Suppress("ktlint:standard:no-multi-spaces")
setupOncenull180         fun setupOnce() {
181             // TODO: assertions thrown in @BeforeClass / @AfterClass are not well supported in the
182             // test infrastructure. Consider saving exception and throwing it in setUp().
183 
184             // APF must run when the screen is off and the device is not interactive.
185             turnScreenOff()
186 
187             // Wait for APF to become active.
188             Thread.sleep(1000)
189             // TODO: check that there is no active wifi network. Otherwise, ApfFilter has already been
190             // created.
191             // APF adb cmds are only implemented in ApfFilter.java. Enable experiment to prevent
192             // LegacyApfFilter.java from being used.
193             runAsShell(WRITE_DEVICE_CONFIG, WRITE_ALLOWLISTED_DEVICE_CONFIG) {
194                 DeviceConfig.setProperty(
195                         NAMESPACE_CONNECTIVITY,
196                         APF_NEW_RA_FILTER_VERSION,
197                         "1",  // value => force enabled
198                         false // makeDefault
199                 )
200             }
201         }
202 
203         @AfterClass
204         @JvmStatic
tearDownOncenull205         fun tearDownOnce() {
206             turnScreenOn()
207         }
208     }
209 
210     class Icmp6PacketReader(
211             handler: Handler,
212             private val network: Network
213     ) : PacketReader(handler, RCV_BUFFER_SIZE) {
214         private var sockFd: FileDescriptor? = null
215         private var futureReply: CompletableFuture<ByteArray>? = null
216 
createFdnull217         override fun createFd(): FileDescriptor {
218             // sockFd is closed by calling super.stop()
219             val sock = Os.socket(AF_INET6, SOCK_DGRAM or SOCK_NONBLOCK, IPPROTO_ICMPV6)
220             // APF runs only on WiFi, so make sure the socket is bound to the right network.
221             network.bindSocket(sock)
222             sockFd = sock
223             return sock
224         }
225 
handlePacketnull226         override fun handlePacket(recvbuf: ByteArray, length: Int) {
227             // If zero-length or Type is not echo reply: ignore.
228             if (length == 0 || recvbuf[0] != 0x81.toByte()) {
229                 return
230             }
231             // Only copy the ping data and complete the future.
232             val result = recvbuf.sliceArray(8..<length)
233             Log.i(TAG, "Received ping reply: ${result.toHexString()}")
234             futureReply!!.complete(recvbuf.sliceArray(8..<length))
235         }
236 
sendPingnull237         fun sendPing(data: ByteArray, payloadSize: Int) {
238             require(data.size == payloadSize)
239 
240             // rfc4443#section-4.1: Echo Request Message
241             //   0                   1                   2                   3
242             //   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
243             //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
244             //  |     Type      |     Code      |          Checksum             |
245             //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
246             //  |           Identifier          |        Sequence Number        |
247             //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
248             //  |     Data ...
249             //  +-+-+-+-+-
250             val icmp6Header = byteArrayOf(0x80.toByte(), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
251             val packet = icmp6Header + data
252             Log.i(TAG, "Sent ping: ${packet.toHexString()}")
253             futureReply = CompletableFuture<ByteArray>()
254             Os.sendto(sockFd!!, packet, 0, packet.size, 0, PING_DESTINATION)
255         }
256 
expectPingReplynull257         fun expectPingReply(timeoutMs: Long = TIMEOUT_MS): ByteArray {
258             return futureReply!!.get(timeoutMs, TimeUnit.MILLISECONDS)
259         }
260 
expectPingDroppednull261         fun expectPingDropped() {
262             assertFailsWith(TimeoutException::class) {
263                 futureReply!!.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
264             }
265         }
266 
startnull267         override fun start(): Boolean {
268             // Ignore the fact start() could return false or throw an exception.
269             handler.post({ super.start() })
270             handler.waitForIdle(TIMEOUT_MS)
271             return true
272         }
273 
stopnull274         override fun stop() {
275             handler.post({ super.stop() })
276             handler.waitForIdle(TIMEOUT_MS)
277         }
278     }
279 
280     @get:Rule val ignoreRule = DevSdkIgnoreRule()
281     @get:Rule val expect = Expect.create()
282 
<lambda>null283     private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! }
<lambda>null284     private val pm by lazy { context.packageManager }
285     private lateinit var network: Network
286     private lateinit var ifname: String
287     private lateinit var networkCallback: TestableNetworkCallback
288     private lateinit var caps: ApfCapabilities
<lambda>null289     private val handlerThread = HandlerThread("$TAG handler thread").apply { start() }
290     private val handler = Handler(handlerThread.looper)
291     private lateinit var packetReader: Icmp6PacketReader
292 
getApfCapabilitiesnull293     fun getApfCapabilities(): ApfCapabilities {
294         val caps = runShellCommand("cmd network_stack apf $ifname capabilities").trim()
295         if (caps.isEmpty()) {
296             return ApfCapabilities(0, 0, 0)
297         }
298         val (version, maxLen, packetFormat) = caps.split(",").map { it.toInt() }
299         return ApfCapabilities(version, maxLen, packetFormat)
300     }
301 
302     @Before
setUpnull303     fun setUp() {
304         assume().that(pm.hasSystemFeature(FEATURE_WIFI)).isTrue()
305 
306         networkCallback = TestableNetworkCallback()
307         cm.requestNetwork(
308                 NetworkRequest.Builder()
309                         .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
310                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
311                         .build(),
312                 networkCallback
313         )
314         network = networkCallback.expect<Available>().network
315         networkCallback.eventuallyExpect<LinkPropertiesChanged>(TIMEOUT_MS) {
316             ifname = assertNotNull(it.lp.interfaceName)
317             true
318         }
319         // It's possible the device does not support APF, in which case this command will not be
320         // successful. Ignore the error as testApfCapabilities() already asserts APF support on the
321         // respective VSR releases and all other tests are based on the capabilities indicated.
322         runShellCommand("cmd network_stack apf $ifname pause")
323         caps = getApfCapabilities()
324 
325         packetReader = Icmp6PacketReader(handler, network)
326         packetReader.start()
327     }
328 
329     @After
tearDownnull330     fun tearDown() {
331         if (::packetReader.isInitialized) {
332             packetReader.stop()
333         }
334         handlerThread.quitSafely()
335         handlerThread.join()
336 
337         if (::ifname.isInitialized) {
338             runShellCommand("cmd network_stack apf $ifname resume")
339         }
340         if (::networkCallback.isInitialized) {
341             cm.unregisterNetworkCallback(networkCallback)
342         }
343     }
344 
345     @VsrTest(
346         requirements = ["VSR-5.3.12-001", "VSR-5.3.12-003", "VSR-5.3.12-004", "VSR-5.3.12-009",
347             "VSR-5.3.12-012"]
348     )
349     @Test
testApfCapabilitiesnull350     fun testApfCapabilities() {
351         // APF became mandatory in Android 14 VSR.
352         assume().that(getVsrApiLevel()).isAtLeast(34)
353 
354         // ApfFilter does not support anything but ARPHRD_ETHER.
355         assertThat(caps.apfPacketFormat).isEqualTo(OsConstants.ARPHRD_ETHER)
356 
357         // DEVICEs launching with Android 14 with CHIPSETs that set ro.board.first_api_level to 34:
358         // - [GMS-VSR-5.3.12-003] MUST return 4 or higher as the APF version number from calls to
359         //   the getApfPacketFilterCapabilities HAL method.
360         // - [GMS-VSR-5.3.12-004] MUST indicate at least 1024 bytes of usable memory from calls to
361         //   the getApfPacketFilterCapabilities HAL method.
362         // TODO: check whether above text should be changed "34 or higher"
363         assertThat(caps.apfVersionSupported).isAtLeast(4)
364         assertThat(caps.maximumApfProgramSize).isAtLeast(1024)
365 
366         if (caps.apfVersionSupported > 4) {
367             assertThat(caps.maximumApfProgramSize).isAtLeast(2048)
368             assertThat(caps.apfVersionSupported).isEqualTo(6000) // v6.0000
369         }
370 
371         // DEVICEs launching with Android 15 (AOSP experimental) or higher with CHIPSETs that set
372         // ro.board.first_api_level or ro.board.api_level to 202404 or higher:
373         // - [GMS-VSR-5.3.12-009] MUST indicate at least 2048 bytes of usable memory from calls to
374         //   the getApfPacketFilterCapabilities HAL method.
375         if (getVsrApiLevel() >= 202404) {
376             assertThat(caps.maximumApfProgramSize).isAtLeast(2048)
377         }
378     }
379 
380     // APF is backwards compatible, i.e. a v6 interpreter supports both v2 and v4 functionality.
assumeApfVersionSupportAtLeastnull381     fun assumeApfVersionSupportAtLeast(version: Int) {
382         assume().that(caps.apfVersionSupported).isAtLeast(version)
383     }
384 
installProgramnull385     fun installProgram(bytes: ByteArray) {
386         val prog = bytes.toHexString()
387         val result = runShellCommandOrThrow("cmd network_stack apf $ifname install $prog").trim()
388         // runShellCommandOrThrow only throws on S+.
389         assertThat(result).isEqualTo("success")
390     }
391 
readProgramnull392     fun readProgram(): ByteArray {
393         val progHexString = runShellCommandOrThrow("cmd network_stack apf $ifname read").trim()
394         // runShellCommandOrThrow only throws on S+.
395         assertThat(progHexString).isNotEmpty()
396         return HexDump.hexStringToByteArray(progHexString)
397     }
398 
399     @VsrTest(
400             requirements = ["VSR-5.3.12-007", "VSR-5.3.12-008", "VSR-5.3.12-010", "VSR-5.3.12-011"]
401     )
402     @SkipPresubmit(reason = "This test takes longer than 1 minute, do not run it on presubmit.")
403     // APF integration is mostly broken before V, only run the full read / write test on V+.
404     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
405     // Increase timeout for test to 15 minutes to accommodate device with large APF RAM.
406     @Test(timeout = 15 * 60 * 1000)
testReadWriteProgramnull407     fun testReadWriteProgram() {
408         assumeApfVersionSupportAtLeast(4)
409 
410         val minReadWriteSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
411             2
412         } else {
413             8
414         }
415 
416         // The minReadWriteSize is 2 bytes. The first byte always stays PASS.
417         val program = ByteArray(caps.maximumApfProgramSize)
418         for (i in caps.maximumApfProgramSize downTo minReadWriteSize) {
419             // Randomize bytes in range [1, i). And install first [0, i) bytes of program.
420             // Note that only the very first instruction (PASS) is valid APF bytecode.
421             Random.nextBytes(program, 1 /* fromIndex */, i /* toIndex */)
422             installProgram(program.sliceArray(0..<i))
423 
424             // Compare entire memory region.
425             val readResult = readProgram()
426             val errMsg = """
427                 read/write $i byte prog failed.
428                 In APFv4, the APF memory region MUST NOT be modified or cleared except by APF
429                 instructions executed by the interpreter or by Android OS calls to the HAL. If this
430                 requirement cannot be met, the firmware cannot declare that it supports APFv4 and
431                 it should declare that it only supports APFv3(if counter is partially supported) or
432                 APFv2.
433             """.trimIndent()
434             assertWithMessage(errMsg).that(readResult).isEqualTo(program)
435         }
436     }
437 
installAndVerifyProgramnull438     private fun installAndVerifyProgram(program: ByteArray) {
439         installProgram(program)
440         val readResult = readProgram().take(program.size).toByteArray()
441         assertThat(readResult).isEqualTo(program)
442     }
443 
addPassIfNotIcmpv6EchoReplynull444     fun ApfV4GeneratorBase<*>.addPassIfNotIcmpv6EchoReply() {
445         // If not IPv6 -> PASS
446         addLoad16(R0, ETH_ETHERTYPE_OFFSET)
447         addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), BaseApfGenerator.PASS_LABEL)
448 
449         // If not ICMPv6 -> PASS
450         addLoad8(R0, IPV6_NEXT_HEADER_OFFSET)
451         addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), BaseApfGenerator.PASS_LABEL)
452 
453         // If not echo reply -> PASS
454         addLoad8(R0, ICMP6_TYPE_OFFSET)
455         addJumpIfR0NotEquals(0x81, BaseApfGenerator.PASS_LABEL)
456     }
457 
458     // APF integration is mostly broken before V
459     @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
460     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
461     @Test
testDropPingReplynull462     fun testDropPingReply() {
463         // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link
464         // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF
465         // should be turned on.
466         assume().that(getVsrApiLevel()).isAtLeast(34)
467         assumeApfVersionSupportAtLeast(4)
468 
469         // clear any active APF filter
470         clearApfMemory()
471         readProgram() // wait for install completion
472 
473         // Assert that initial ping does not get filtered.
474         val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
475             68
476         } else {
477             4
478         }
479         val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
480         packetReader.sendPing(data, payloadSize)
481         assertThat(packetReader.expectPingReply()).isEqualTo(data)
482 
483         // Generate an APF program that drops the next ping
484         val gen = ApfV4Generator(
485                 caps.apfVersionSupported,
486                 caps.maximumApfProgramSize,
487                 caps.maximumApfProgramSize
488         )
489 
490         // If not ICMPv6 Echo Reply -> PASS
491         gen.addPassIfNotIcmpv6EchoReply()
492 
493         // if not data matches -> PASS
494         gen.addLoadImmediate(R0, ICMP6_TYPE_OFFSET + PING_HEADER_LENGTH)
495         gen.addJumpIfBytesAtR0NotEqual(data, BaseApfGenerator.PASS_LABEL)
496 
497         // else DROP
498         gen.addJump(BaseApfGenerator.DROP_LABEL)
499 
500         val program = gen.generate()
501         installAndVerifyProgram(program)
502 
503         packetReader.sendPing(data, payloadSize)
504         packetReader.expectPingDropped()
505     }
506 
clearApfMemorynull507     fun clearApfMemory() = installProgram(ByteArray(caps.maximumApfProgramSize))
508 
509     // APF integration is mostly broken before V
510     @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
511     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
512     @Test
513     fun testPrefilledMemorySlotsV4() {
514         // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link
515         // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF
516         // should be turned on.
517         assume().that(getVsrApiLevel()).isAtLeast(34)
518         // Test v4 memory slots on both v4 and v6 interpreters.
519         assumeApfVersionSupportAtLeast(4)
520         clearApfMemory()
521         val gen = ApfV4Generator(
522                 caps.apfVersionSupported,
523                 caps.maximumApfProgramSize,
524                 caps.maximumApfProgramSize
525         )
526 
527         // If not ICMPv6 Echo Reply -> PASS
528         gen.addPassIfNotIcmpv6EchoReply()
529 
530         // Store all prefilled memory slots in counter region [500, 520)
531         val counterRegion = 500
532         gen.addLoadImmediate(R1, counterRegion)
533         gen.addLoadFromMemory(R0, MemorySlot.PROGRAM_SIZE)
534         gen.addStoreData(R0, 0)
535         gen.addLoadFromMemory(R0, MemorySlot.RAM_LEN)
536         gen.addStoreData(R0, 4)
537         gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE)
538         gen.addStoreData(R0, 8)
539         gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
540         gen.addStoreData(R0, 12)
541         gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS)
542         gen.addStoreData(R0, 16)
543 
544         val program = gen.generate()
545         assertThat(program.size).isLessThan(counterRegion)
546         installAndVerifyProgram(program)
547 
548         // Trigger the program by sending a ping and waiting on the reply.
549         val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
550             68
551         } else {
552             4
553         }
554         val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
555         packetReader.sendPing(data, payloadSize)
556         packetReader.expectPingReply()
557 
558         val readResult = readProgram()
559         val buffer = ByteBuffer.wrap(readResult, counterRegion, 20 /* length */)
560         expect.withMessage("PROGRAM_SIZE").that(buffer.getInt()).isEqualTo(program.size)
561         expect.withMessage("RAM_LEN").that(buffer.getInt()).isEqualTo(caps.maximumApfProgramSize)
562         expect.withMessage("IPV4_HEADER_SIZE").that(buffer.getInt()).isEqualTo(0)
563         // Ping packet payload + ICMPv6 header (8)  + IPv6 header (40) + ethernet header (14)
564         expect.withMessage("PACKET_SIZE").that(buffer.getInt()).isEqualTo(payloadSize + 8 + 40 + 14)
565         expect.withMessage("FILTER_AGE_SECONDS").that(buffer.getInt()).isLessThan(5)
566     }
567 
568     // APF integration is mostly broken before V
569     @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
570     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
571     @Test
testFilterAgeIncreasesBetweenPacketsnull572     fun testFilterAgeIncreasesBetweenPackets() {
573         // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link
574         // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF
575         // should be turned on.
576         assume().that(getVsrApiLevel()).isAtLeast(34)
577         assumeApfVersionSupportAtLeast(4)
578         clearApfMemory()
579         val gen = ApfV4Generator(
580                 caps.apfVersionSupported,
581                 caps.maximumApfProgramSize,
582                 caps.maximumApfProgramSize
583         )
584 
585         // If not ICMPv6 Echo Reply -> PASS
586         gen.addPassIfNotIcmpv6EchoReply()
587 
588         // Store all prefilled memory slots in counter region [500, 520)
589         val counterRegion = 500
590         gen.addLoadImmediate(R1, counterRegion)
591         gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS)
592         gen.addStoreData(R0, 0)
593 
594         installAndVerifyProgram(gen.generate())
595 
596         val payloadSize = 56
597         val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
598         packetReader.sendPing(data, payloadSize)
599         packetReader.expectPingReply()
600 
601         var buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */)
602         val filterAgeSecondsOrig = buffer.getInt()
603 
604         Thread.sleep(5100)
605 
606         packetReader.sendPing(data, payloadSize)
607         packetReader.expectPingReply()
608 
609         buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */)
610         val filterAgeSeconds = buffer.getInt()
611         // Assert that filter age has increased, but not too much.
612         val timeDiff = filterAgeSeconds - filterAgeSecondsOrig
613         assertThat(timeDiff).isAnyOf(5, 6)
614     }
615 
616     @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
617     @Test
testFilterAge16384thsIncreasesBetweenPacketsnull618     fun testFilterAge16384thsIncreasesBetweenPackets() {
619         assumeApfVersionSupportAtLeast(6000)
620         clearApfMemory()
621         val gen = ApfV6Generator(
622                 caps.apfVersionSupported,
623                 caps.maximumApfProgramSize,
624                 caps.maximumApfProgramSize
625         )
626 
627         // If not ICMPv6 Echo Reply -> PASS
628         gen.addPassIfNotIcmpv6EchoReply()
629 
630         // Store all prefilled memory slots in counter region [500, 520)
631         gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_16384THS)
632         gen.addStoreCounter(FILTER_AGE_16384THS, R0)
633 
634         installAndVerifyProgram(gen.generate())
635 
636         val payloadSize = 56
637         val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
638         packetReader.sendPing(data, payloadSize)
639         packetReader.expectPingReply()
640 
641         var apfRam = readProgram()
642         val filterAge16384thSecondsOrig =
643                 ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS)
644 
645         Thread.sleep(5000)
646 
647         packetReader.sendPing(data, payloadSize)
648         packetReader.expectPingReply()
649 
650         apfRam = readProgram()
651         val filterAge16384thSeconds = ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS)
652         val timeDiff = (filterAge16384thSeconds - filterAge16384thSecondsOrig)
653         // Expect the HAL plus ping latency to be less than 800ms.
654         val timeDiffLowerBound = (4.99 * 16384).toInt()
655         val timeDiffUpperBound = (5.81 * 16384).toInt()
656         // Assert that filter age has increased, but not too much.
657         assertThat(timeDiff).isGreaterThan(timeDiffLowerBound)
658         assertThat(timeDiff).isLessThan(timeDiffUpperBound)
659     }
660 
661     @VsrTest(
662             requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005", "VSR-5.3.12-012", "VSR-5.3.12-013",
663                 "VSR-5.3.12-014", "VSR-5.3.12-015", "VSR-5.3.12-016", "VSR-5.3.12-017"]
664     )
665     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
666     @Test
testReplyPingnull667     fun testReplyPing() {
668         assumeApfVersionSupportAtLeast(6000)
669         installProgram(ByteArray(caps.maximumApfProgramSize) { 0 }) // Clear previous program
670         readProgram() // Ensure installation is complete
671 
672         val payloadSize = 56
673         val payload = ByteArray(payloadSize).also { Random.nextBytes(it) }
674         val firstByte = payload.take(1).toByteArray()
675 
676         val pingRequestIpv6PayloadLen = PING_HEADER_LENGTH + 1
677         val pingRequestPktLen = ETH_HEADER_LEN + IPV6_HEADER_LEN + pingRequestIpv6PayloadLen
678 
679         val gen = ApfV6Generator(
680                 caps.apfVersionSupported,
681                 caps.maximumApfProgramSize,
682                 caps.maximumApfProgramSize
683         )
684         val skipPacketLabel = gen.uniqueLabel
685 
686         // Summary of the program:
687         //   if the packet is not ICMPv6 echo reply
688         //     pass
689         //   else if the echo reply payload size is 1
690         //     increase PASSED_IPV6_ICMP counter
691         //     pass
692         //   else
693         //     transmit a ICMPv6 echo request packet with the first byte of the payload in the reply
694         //     increase DROPPED_IPV6_MULTICAST_PING counter
695         //     drop
696         val program = gen
697                 .addLoad16(R0, ETH_ETHERTYPE_OFFSET)
698                 .addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), skipPacketLabel)
699                 .addLoad8(R0, IPV6_NEXT_HEADER_OFFSET)
700                 .addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), skipPacketLabel)
701                 .addLoad8(R0, ICMP6_TYPE_OFFSET)
702                 .addJumpIfR0NotEquals(0x81, skipPacketLabel) // Echo reply type
703                 .addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
704                 .addCountAndPassIfR0Equals(
705                         (ETHER_HEADER_LEN + IPV6_HEADER_LEN + PING_HEADER_LENGTH + firstByte.size)
706                                 .toLong(),
707                         PASSED_IPV6_ICMP
708                 )
709                 // Ping Packet Generation
710                 .addAllocate(pingRequestPktLen)
711                 // Eth header
712                 .addPacketCopy(ETHER_SRC_ADDR_OFFSET, ETHER_ADDR_LEN) // dst MAC address
713                 .addPacketCopy(ETHER_DST_ADDR_OFFSET, ETHER_ADDR_LEN) // src MAC address
714                 .addWriteU16(ETH_P_IPV6) // IPv6 type
715                 // IPv6 Header
716                 .addWrite32(0x60000000) // IPv6 Header: version, traffic class, flowlabel
717                 // payload length (2 bytes) | next header: ICMPv6 (1 byte) | hop limit (1 byte)
718                 .addWrite32(pingRequestIpv6PayloadLen shl 16 or (IPPROTO_ICMPV6 shl 8 or 64))
719                 .addPacketCopy(IPV6_DEST_ADDR_OFFSET, IPV6_ADDR_LEN) // src ip
720                 .addPacketCopy(IPV6_SRC_ADDR_OFFSET, IPV6_ADDR_LEN) // dst ip
721                 // ICMPv6
722                 .addWriteU8(0x80) // type: echo request
723                 .addWriteU8(0) // code
724                 .addWriteU16(pingRequestIpv6PayloadLen) // checksum
725                 // identifier
726                 .addPacketCopy(ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_HEADER_MIN_LEN, 2)
727                 .addWriteU16(0) // sequence number
728                 .addDataCopy(firstByte) // data
729                 .addTransmitL4(
730                         ETHER_HEADER_LEN, // ip_ofs
731                         ICMP6_CHECKSUM_OFFSET, // csum_ofs
732                         IPV6_SRC_ADDR_OFFSET, // csum_start
733                         IPPROTO_ICMPV6, // partial_sum
734                         false // udp
735                 )
736                 // Warning: the program abuse DROPPED_IPV6_MULTICAST_PING for debugging purpose
737                 .addCountAndDrop(DROPPED_IPV6_MULTICAST_PING)
738                 .defineLabel(skipPacketLabel)
739                 .addPass()
740                 .generate()
741 
742         installAndVerifyProgram(program)
743 
744         packetReader.sendPing(payload, payloadSize)
745 
746         val replyPayload = try {
747             packetReader.expectPingReply(TIMEOUT_MS * 2)
748         } catch (e: TimeoutException) {
749             byteArrayOf() // Empty payload if timeout occurs
750         }
751 
752         val apfCounterTracker = ApfCounterTracker()
753         apfCounterTracker.updateCountersFromData(readProgram())
754         Log.i(TAG, "counter map: ${apfCounterTracker.counters}")
755 
756         assertThat(replyPayload).isEqualTo(firstByte)
757     }
758 }
759