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_DEVICE_CONFIG 23 import android.content.pm.PackageManager.FEATURE_WIFI 24 import android.net.ConnectivityManager 25 import android.net.Network 26 import android.net.NetworkCapabilities 27 import android.net.NetworkRequest 28 import android.net.apf.ApfCapabilities 29 import android.net.apf.ApfConstants.ETH_ETHERTYPE_OFFSET 30 import android.net.apf.ApfConstants.ICMP6_TYPE_OFFSET 31 import android.net.apf.ApfConstants.IPV6_NEXT_HEADER_OFFSET 32 import android.net.apf.ApfV4Generator 33 import android.net.apf.BaseApfGenerator 34 import android.net.apf.BaseApfGenerator.MemorySlot 35 import android.net.apf.BaseApfGenerator.Register.R0 36 import android.net.apf.BaseApfGenerator.Register.R1 37 import android.os.Build 38 import android.os.Handler 39 import android.os.HandlerThread 40 import android.os.PowerManager 41 import android.platform.test.annotations.AppModeFull 42 import android.provider.DeviceConfig 43 import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY 44 import android.system.Os 45 import android.system.OsConstants 46 import android.system.OsConstants.AF_INET6 47 import android.system.OsConstants.ETH_P_IPV6 48 import android.system.OsConstants.IPPROTO_ICMPV6 49 import android.system.OsConstants.SOCK_DGRAM 50 import android.system.OsConstants.SOCK_NONBLOCK 51 import android.util.Log 52 import androidx.test.filters.RequiresDevice 53 import androidx.test.platform.app.InstrumentationRegistry 54 import com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel 55 import com.android.compatibility.common.util.PropertyUtil.getVsrApiLevel 56 import com.android.compatibility.common.util.SystemUtil.runShellCommand 57 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow 58 import com.android.compatibility.common.util.VsrTest 59 import com.android.internal.util.HexDump 60 import com.android.net.module.util.PacketReader 61 import com.android.testutils.DevSdkIgnoreRule 62 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo 63 import com.android.testutils.DevSdkIgnoreRunner 64 import com.android.testutils.NetworkStackModuleTest 65 import com.android.testutils.RecorderCallback.CallbackEntry.Available 66 import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged 67 import com.android.testutils.SkipPresubmit 68 import com.android.testutils.TestableNetworkCallback 69 import com.android.testutils.runAsShell 70 import com.android.testutils.waitForIdle 71 import com.google.common.truth.Expect 72 import com.google.common.truth.Truth.assertThat 73 import com.google.common.truth.Truth.assertWithMessage 74 import com.google.common.truth.TruthJUnit.assume 75 import java.io.FileDescriptor 76 import java.lang.Thread 77 import java.net.InetSocketAddress 78 import java.nio.ByteBuffer 79 import java.util.concurrent.CompletableFuture 80 import java.util.concurrent.TimeUnit 81 import java.util.concurrent.TimeoutException 82 import kotlin.random.Random 83 import kotlin.test.assertFailsWith 84 import kotlin.test.assertNotNull 85 import org.junit.After 86 import org.junit.AfterClass 87 import org.junit.Before 88 import org.junit.BeforeClass 89 import org.junit.Rule 90 import org.junit.Test 91 import org.junit.runner.RunWith 92 93 private const val TAG = "ApfIntegrationTest" 94 private const val TIMEOUT_MS = 2000L 95 private const val APF_NEW_RA_FILTER_VERSION = "apf_new_ra_filter_version" 96 private const val POLLING_INTERVAL_MS: Int = 100 97 private const val RCV_BUFFER_SIZE = 1480 98 private const val PING_HEADER_LENGTH = 8 99 100 @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps") 101 @RunWith(DevSdkIgnoreRunner::class) 102 @RequiresDevice 103 @NetworkStackModuleTest 104 // ByteArray.toHexString is experimental API 105 @kotlin.ExperimentalStdlibApi 106 class ApfIntegrationTest { 107 companion object { 108 private val PING_DESTINATION = InetSocketAddress("2001:4860:4860::8888", 0) 109 110 private val context = InstrumentationRegistry.getInstrumentation().context 111 private val powerManager = context.getSystemService(PowerManager::class.java)!! 112 private val wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG) 113 pollingChecknull114 fun pollingCheck(condition: () -> Boolean, timeout_ms: Int): Boolean { 115 var polling_time = 0 116 do { 117 Thread.sleep(POLLING_INTERVAL_MS.toLong()) 118 polling_time += POLLING_INTERVAL_MS 119 if (condition()) return true 120 } while (polling_time < timeout_ms) 121 return false 122 } 123 turnScreenOffnull124 fun turnScreenOff() { 125 if (!wakeLock.isHeld()) wakeLock.acquire() 126 runShellCommandOrThrow("input keyevent KEYCODE_SLEEP") 127 val result = pollingCheck({ !powerManager.isInteractive() }, timeout_ms = 2000) 128 assertThat(result).isTrue() 129 } 130 turnScreenOnnull131 fun turnScreenOn() { 132 if (wakeLock.isHeld()) wakeLock.release() 133 runShellCommandOrThrow("input keyevent KEYCODE_WAKEUP") 134 val result = pollingCheck({ powerManager.isInteractive() }, timeout_ms = 2000) 135 assertThat(result).isTrue() 136 } 137 138 @BeforeClass 139 @JvmStatic 140 @Suppress("ktlint:standard:no-multi-spaces") setupOncenull141 fun setupOnce() { 142 // TODO: assertions thrown in @BeforeClass / @AfterClass are not well supported in the 143 // test infrastructure. Consider saving excepion and throwing it in setUp(). 144 // APF must run when the screen is off and the device is not interactive. 145 turnScreenOff() 146 // Wait for APF to become active. 147 Thread.sleep(1000) 148 // TODO: check that there is no active wifi network. Otherwise, ApfFilter has already been 149 // created. 150 // APF adb cmds are only implemented in ApfFilter.java. Enable experiment to prevent 151 // LegacyApfFilter.java from being used. 152 runAsShell(WRITE_DEVICE_CONFIG) { 153 DeviceConfig.setProperty( 154 NAMESPACE_CONNECTIVITY, 155 APF_NEW_RA_FILTER_VERSION, 156 "1", // value => force enabled 157 false // makeDefault 158 ) 159 } 160 } 161 162 @AfterClass 163 @JvmStatic tearDownOncenull164 fun tearDownOnce() { 165 turnScreenOn() 166 } 167 } 168 169 class Icmp6PacketReader( 170 handler: Handler, 171 private val network: Network 172 ) : PacketReader(handler, RCV_BUFFER_SIZE) { 173 private var sockFd: FileDescriptor? = null 174 private var futureReply: CompletableFuture<ByteArray>? = null 175 createFdnull176 override fun createFd(): FileDescriptor { 177 // sockFd is closed by calling super.stop() 178 val sock = Os.socket(AF_INET6, SOCK_DGRAM or SOCK_NONBLOCK, IPPROTO_ICMPV6) 179 // APF runs only on WiFi, so make sure the socket is bound to the right network. 180 network.bindSocket(sock) 181 sockFd = sock 182 return sock 183 } 184 handlePacketnull185 override fun handlePacket(recvbuf: ByteArray, length: Int) { 186 // If zero-length or Type is not echo reply: ignore. 187 if (length == 0 || recvbuf[0] != 0x81.toByte()) { 188 return 189 } 190 // Only copy the ping data and complete the future. 191 val result = recvbuf.sliceArray(8..<length) 192 Log.i(TAG, "Received ping reply: ${result.toHexString()}") 193 futureReply!!.complete(recvbuf.sliceArray(8..<length)) 194 } 195 sendPingnull196 fun sendPing(data: ByteArray, payloadSize: Int) { 197 require(data.size == payloadSize) 198 199 // rfc4443#section-4.1: Echo Request Message 200 // 0 1 2 3 201 // 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 202 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 203 // | Type | Code | Checksum | 204 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 205 // | Identifier | Sequence Number | 206 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 207 // | Data ... 208 // +-+-+-+-+- 209 val icmp6Header = byteArrayOf(0x80.toByte(), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) 210 val packet = icmp6Header + data 211 Log.i(TAG, "Sent ping: ${packet.toHexString()}") 212 futureReply = CompletableFuture<ByteArray>() 213 Os.sendto(sockFd!!, packet, 0, packet.size, 0, PING_DESTINATION) 214 } 215 expectPingReplynull216 fun expectPingReply(): ByteArray { 217 return futureReply!!.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) 218 } 219 expectPingDroppednull220 fun expectPingDropped() { 221 assertFailsWith(TimeoutException::class) { 222 futureReply!!.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) 223 } 224 } 225 startnull226 override fun start(): Boolean { 227 // Ignore the fact start() could return false or throw an exception. 228 handler.post({ super.start() }) 229 handler.waitForIdle(TIMEOUT_MS) 230 return true 231 } 232 stopnull233 override fun stop() { 234 handler.post({ super.stop() }) 235 handler.waitForIdle(TIMEOUT_MS) 236 } 237 } 238 239 @get:Rule val ignoreRule = DevSdkIgnoreRule() 240 @get:Rule val expect = Expect.create() 241 <lambda>null242 private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! } <lambda>null243 private val pm by lazy { context.packageManager } 244 private lateinit var network: Network 245 private lateinit var ifname: String 246 private lateinit var networkCallback: TestableNetworkCallback 247 private lateinit var caps: ApfCapabilities <lambda>null248 private val handlerThread = HandlerThread("$TAG handler thread").apply { start() } 249 private val handler = Handler(handlerThread.looper) 250 private lateinit var packetReader: Icmp6PacketReader 251 getApfCapabilitiesnull252 fun getApfCapabilities(): ApfCapabilities { 253 val caps = runShellCommand("cmd network_stack apf $ifname capabilities").trim() 254 if (caps.isEmpty()) { 255 return ApfCapabilities(0, 0, 0) 256 } 257 val (version, maxLen, packetFormat) = caps.split(",").map { it.toInt() } 258 return ApfCapabilities(version, maxLen, packetFormat) 259 } 260 261 @Before setUpnull262 fun setUp() { 263 assume().that(pm.hasSystemFeature(FEATURE_WIFI)).isTrue() 264 265 networkCallback = TestableNetworkCallback() 266 cm.requestNetwork( 267 NetworkRequest.Builder() 268 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 269 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 270 .build(), 271 networkCallback 272 ) 273 network = networkCallback.expect<Available>().network 274 networkCallback.eventuallyExpect<LinkPropertiesChanged>(TIMEOUT_MS) { 275 ifname = assertNotNull(it.lp.interfaceName) 276 true 277 } 278 // It's possible the device does not support APF, in which case this command will not be 279 // successful. Ignore the error as testApfCapabilities() already asserts APF support on the 280 // respective VSR releases and all other tests are based on the capabilities indicated. 281 runShellCommand("cmd network_stack apf $ifname pause") 282 caps = getApfCapabilities() 283 284 packetReader = Icmp6PacketReader(handler, network) 285 packetReader.start() 286 } 287 288 @After tearDownnull289 fun tearDown() { 290 if (::packetReader.isInitialized) { 291 packetReader.stop() 292 } 293 handlerThread.quitSafely() 294 handlerThread.join() 295 296 if (::ifname.isInitialized) { 297 runShellCommand("cmd network_stack apf $ifname resume") 298 } 299 if (::networkCallback.isInitialized) { 300 cm.unregisterNetworkCallback(networkCallback) 301 } 302 } 303 304 @VsrTest( 305 requirements = ["VSR-5.3.12-001", "VSR-5.3.12-003", "VSR-5.3.12-004", "VSR-5.3.12-009", 306 "VSR-5.3.12-012"] 307 ) 308 @Test testApfCapabilitiesnull309 fun testApfCapabilities() { 310 // APF became mandatory in Android 14 VSR. 311 assume().that(getVsrApiLevel()).isAtLeast(34) 312 313 // ApfFilter does not support anything but ARPHRD_ETHER. 314 assertThat(caps.apfPacketFormat).isEqualTo(OsConstants.ARPHRD_ETHER) 315 316 // DEVICEs launching with Android 14 with CHIPSETs that set ro.board.first_api_level to 34: 317 // - [GMS-VSR-5.3.12-003] MUST return 4 or higher as the APF version number from calls to 318 // the getApfPacketFilterCapabilities HAL method. 319 // - [GMS-VSR-5.3.12-004] MUST indicate at least 1024 bytes of usable memory from calls to 320 // the getApfPacketFilterCapabilities HAL method. 321 // TODO: check whether above text should be changed "34 or higher" 322 assertThat(caps.apfVersionSupported).isAtLeast(4) 323 assertThat(caps.maximumApfProgramSize).isAtLeast(1024) 324 325 if (caps.apfVersionSupported > 4) { 326 assertThat(caps.maximumApfProgramSize).isAtLeast(2048) 327 assertThat(caps.apfVersionSupported).isEqualTo(6000) // v6.0000 328 } 329 330 // DEVICEs launching with Android 15 (AOSP experimental) or higher with CHIPSETs that set 331 // ro.board.first_api_level or ro.board.api_level to 202404 or higher: 332 // - [GMS-VSR-5.3.12-009] MUST indicate at least 2048 bytes of usable memory from calls to 333 // the getApfPacketFilterCapabilities HAL method. 334 if (getVsrApiLevel() >= 202404) { 335 assertThat(caps.maximumApfProgramSize).isAtLeast(2048) 336 } 337 } 338 339 // APF is backwards compatible, i.e. a v6 interpreter supports both v2 and v4 functionality. assumeApfVersionSupportAtLeastnull340 fun assumeApfVersionSupportAtLeast(version: Int) { 341 assume().that(caps.apfVersionSupported).isAtLeast(version) 342 } 343 installProgramnull344 fun installProgram(bytes: ByteArray) { 345 val prog = bytes.toHexString() 346 val result = runShellCommandOrThrow("cmd network_stack apf $ifname install $prog").trim() 347 // runShellCommandOrThrow only throws on S+. 348 assertThat(result).isEqualTo("success") 349 } 350 readProgramnull351 fun readProgram(): ByteArray { 352 val progHexString = runShellCommandOrThrow("cmd network_stack apf $ifname read").trim() 353 // runShellCommandOrThrow only throws on S+. 354 assertThat(progHexString).isNotEmpty() 355 return HexDump.hexStringToByteArray(progHexString) 356 } 357 358 @VsrTest( 359 requirements = ["VSR-5.3.12-007", "VSR-5.3.12-008", "VSR-5.3.12-010", "VSR-5.3.12-011"] 360 ) 361 @SkipPresubmit(reason = "This test takes longer than 1 minute, do not run it on presubmit.") 362 // APF integration is mostly broken before V, only run the full read / write test on V+. 363 @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 364 // Increase timeout for test to 15 minutes to accommodate device with large APF RAM. 365 @Test(timeout = 15 * 60 * 1000) testReadWriteProgramnull366 fun testReadWriteProgram() { 367 assumeApfVersionSupportAtLeast(4) 368 369 val minReadWriteSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { 370 2 371 } else { 372 8 373 } 374 375 // The minReadWriteSize is 2 bytes. The first byte always stays PASS. 376 val program = ByteArray(caps.maximumApfProgramSize) 377 for (i in caps.maximumApfProgramSize downTo minReadWriteSize) { 378 // Randomize bytes in range [1, i). And install first [0, i) bytes of program. 379 // Note that only the very first instruction (PASS) is valid APF bytecode. 380 Random.nextBytes(program, 1 /* fromIndex */, i /* toIndex */) 381 installProgram(program.sliceArray(0..<i)) 382 383 // Compare entire memory region. 384 val readResult = readProgram() 385 val errMsg = """ 386 read/write $i byte prog failed. 387 In APFv4, the APF memory region MUST NOT be modified or cleared except by APF 388 instructions executed by the interpreter or by Android OS calls to the HAL. If this 389 requirement cannot be met, the firmware cannot declare that it supports APFv4 and 390 it should declare that it only supports APFv3(if counter is partially supported) or 391 APFv2. 392 """.trimIndent() 393 assertWithMessage(errMsg).that(readResult).isEqualTo(program) 394 } 395 } 396 addPassIfNotIcmpv6EchoReplynull397 fun ApfV4Generator.addPassIfNotIcmpv6EchoReply() { 398 // If not IPv6 -> PASS 399 addLoad16(R0, ETH_ETHERTYPE_OFFSET) 400 addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), BaseApfGenerator.PASS_LABEL) 401 402 // If not ICMPv6 -> PASS 403 addLoad8(R0, IPV6_NEXT_HEADER_OFFSET) 404 addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), BaseApfGenerator.PASS_LABEL) 405 406 // If not echo reply -> PASS 407 addLoad8(R0, ICMP6_TYPE_OFFSET) 408 addJumpIfR0NotEquals(0x81, BaseApfGenerator.PASS_LABEL) 409 } 410 411 // APF integration is mostly broken before V 412 @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"]) 413 @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 414 @Test testDropPingReplynull415 fun testDropPingReply() { 416 // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link 417 // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF 418 // should be turned on. 419 assume().that(getVsrApiLevel()).isAtLeast(34) 420 assumeApfVersionSupportAtLeast(4) 421 422 // clear any active APF filter 423 clearApfMemory() 424 readProgram() // wait for install completion 425 426 // Assert that initial ping does not get filtered. 427 val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { 428 68 429 } else { 430 4 431 } 432 val data = ByteArray(payloadSize).also { Random.nextBytes(it) } 433 packetReader.sendPing(data, payloadSize) 434 assertThat(packetReader.expectPingReply()).isEqualTo(data) 435 436 // Generate an APF program that drops the next ping 437 val gen = ApfV4Generator(4) 438 439 // If not ICMPv6 Echo Reply -> PASS 440 gen.addPassIfNotIcmpv6EchoReply() 441 442 // if not data matches -> PASS 443 gen.addLoadImmediate(R0, ICMP6_TYPE_OFFSET + PING_HEADER_LENGTH) 444 gen.addJumpIfBytesAtR0NotEqual(data, BaseApfGenerator.PASS_LABEL) 445 446 // else DROP 447 gen.addJump(BaseApfGenerator.DROP_LABEL) 448 449 val program = gen.generate() 450 installProgram(program) 451 readProgram() // wait for install completion 452 453 packetReader.sendPing(data, payloadSize) 454 packetReader.expectPingDropped() 455 } 456 clearApfMemorynull457 fun clearApfMemory() = installProgram(ByteArray(caps.maximumApfProgramSize)) 458 459 // APF integration is mostly broken before V 460 @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"]) 461 @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 462 @Test 463 fun testPrefilledMemorySlotsV4() { 464 // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link 465 // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF 466 // should be turned on. 467 assume().that(getVsrApiLevel()).isAtLeast(34) 468 // Test v4 memory slots on both v4 and v6 interpreters. 469 assumeApfVersionSupportAtLeast(4) 470 clearApfMemory() 471 val gen = ApfV4Generator(4) 472 473 // If not ICMPv6 Echo Reply -> PASS 474 gen.addPassIfNotIcmpv6EchoReply() 475 476 // Store all prefilled memory slots in counter region [500, 520) 477 val counterRegion = 500 478 gen.addLoadImmediate(R1, counterRegion) 479 gen.addLoadFromMemory(R0, MemorySlot.PROGRAM_SIZE) 480 gen.addStoreData(R0, 0) 481 gen.addLoadFromMemory(R0, MemorySlot.RAM_LEN) 482 gen.addStoreData(R0, 4) 483 gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE) 484 gen.addStoreData(R0, 8) 485 gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE) 486 gen.addStoreData(R0, 12) 487 gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS) 488 gen.addStoreData(R0, 16) 489 490 val program = gen.generate() 491 assertThat(program.size).isLessThan(counterRegion) 492 installProgram(program) 493 readProgram() // wait for install completion 494 495 // Trigger the program by sending a ping and waiting on the reply. 496 val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { 497 68 498 } else { 499 4 500 } 501 val data = ByteArray(payloadSize).also { Random.nextBytes(it) } 502 packetReader.sendPing(data, payloadSize) 503 packetReader.expectPingReply() 504 505 val readResult = readProgram() 506 val buffer = ByteBuffer.wrap(readResult, counterRegion, 20 /* length */) 507 expect.withMessage("PROGRAM_SIZE").that(buffer.getInt()).isEqualTo(program.size) 508 expect.withMessage("RAM_LEN").that(buffer.getInt()).isEqualTo(caps.maximumApfProgramSize) 509 expect.withMessage("IPV4_HEADER_SIZE").that(buffer.getInt()).isEqualTo(0) 510 // Ping packet payload + ICMPv6 header (8) + IPv6 header (40) + ethernet header (14) 511 expect.withMessage("PACKET_SIZE").that(buffer.getInt()).isEqualTo(payloadSize + 8 + 40 + 14) 512 expect.withMessage("FILTER_AGE_SECONDS").that(buffer.getInt()).isLessThan(5) 513 } 514 515 // APF integration is mostly broken before V 516 @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"]) 517 @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 518 @Test testFilterAgeIncreasesBetweenPacketsnull519 fun testFilterAgeIncreasesBetweenPackets() { 520 // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link 521 // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF 522 // should be turned on. 523 assume().that(getVsrApiLevel()).isAtLeast(34) 524 assumeApfVersionSupportAtLeast(4) 525 clearApfMemory() 526 val gen = ApfV4Generator(4) 527 528 // If not ICMPv6 Echo Reply -> PASS 529 gen.addPassIfNotIcmpv6EchoReply() 530 531 // Store all prefilled memory slots in counter region [500, 520) 532 val counterRegion = 500 533 gen.addLoadImmediate(R1, counterRegion) 534 gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS) 535 gen.addStoreData(R0, 0) 536 537 installProgram(gen.generate()) 538 readProgram() // wait for install completion 539 540 val payloadSize = 56 541 val data = ByteArray(payloadSize).also { Random.nextBytes(it) } 542 packetReader.sendPing(data, payloadSize) 543 packetReader.expectPingReply() 544 545 var buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */) 546 val filterAgeSecondsOrig = buffer.getInt() 547 548 Thread.sleep(5100) 549 550 packetReader.sendPing(data, payloadSize) 551 packetReader.expectPingReply() 552 553 buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */) 554 val filterAgeSeconds = buffer.getInt() 555 // Assert that filter age has increased, but not too much. 556 val timeDiff = filterAgeSeconds - filterAgeSecondsOrig 557 assertThat(timeDiff).isAnyOf(5, 6) 558 } 559 } 560