diff options
Diffstat (limited to 'common/testutils/devicetests/com/android')
46 files changed, 0 insertions, 6106 deletions
diff --git a/common/testutils/devicetests/com/android/testutils/ArpResponder.kt b/common/testutils/devicetests/com/android/testutils/ArpResponder.kt deleted file mode 100644 index cf0490c5..00000000 --- a/common/testutils/devicetests/com/android/testutils/ArpResponder.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.net.MacAddress -import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN -import java.net.Inet4Address -import java.net.InetAddress -import java.nio.ByteBuffer - -private const val ARP_SENDER_MAC_OFFSET = ETHER_HEADER_LEN + 8 -private const val ARP_TARGET_IPADDR_OFFSET = ETHER_HEADER_LEN + 24 - -private val TYPE_ARP = byteArrayOf(0x08, 0x06) -// Arp reply header for IPv4 over ethernet -private val ARP_REPLY_IPV4 = byteArrayOf(0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x02) - -/** - * A class that can be used to reply to ARP packets on a [TapPacketReader]. - */ -class ArpResponder( - reader: TapPacketReader, - table: Map<Inet4Address, MacAddress>, - name: String = ArpResponder::class.java.simpleName -) : PacketResponder(reader, ArpRequestFilter(), name) { - // Copy the map if not already immutable (toMap) to make sure it is not modified - private val table = table.toMap() - - override fun replyToPacket(packet: ByteArray, reader: TapPacketReader) { - val targetIp = InetAddress.getByAddress( - packet.copyFromIndexWithLength(ARP_TARGET_IPADDR_OFFSET, 4)) - as Inet4Address - - val macAddr = table[targetIp]?.toByteArray() ?: return - val senderMac = packet.copyFromIndexWithLength(ARP_SENDER_MAC_OFFSET, 6) - reader.sendResponse(ByteBuffer.wrap( - // Ethernet header - senderMac + macAddr + TYPE_ARP + - // ARP message - ARP_REPLY_IPV4 + - macAddr /* sender MAC */ + - targetIp.address /* sender IP addr */ + - macAddr /* target mac */ + - targetIp.address /* target IP addr */ - )) - } -} - -private fun ByteArray.copyFromIndexWithLength(start: Int, len: Int) = - copyOfRange(start, start + len) diff --git a/common/testutils/devicetests/com/android/testutils/CompatUtil.kt b/common/testutils/devicetests/com/android/testutils/CompatUtil.kt deleted file mode 100644 index 82f1d9b9..00000000 --- a/common/testutils/devicetests/com/android/testutils/CompatUtil.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.net.NetworkSpecifier -import com.android.modules.utils.build.SdkLevel.isAtLeastS - -/** - * Test utility to create [NetworkSpecifier]s on different SDK versions. - */ -object CompatUtil { - @JvmStatic - fun makeTestNetworkSpecifier(ifName: String): NetworkSpecifier { - // Until R, there was no TestNetworkSpecifier, StringNetworkSpecifier was used instead - if (!isAtLeastS()) { - return makeNetworkSpecifierInternal("android.net.StringNetworkSpecifier", ifName) - } - // TestNetworkSpecifier is not part of the SDK in some branches using this utility - // TODO: replace with a direct call to the constructor - return makeNetworkSpecifierInternal("android.net.TestNetworkSpecifier", ifName) - } - - @JvmStatic - fun makeEthernetNetworkSpecifier(ifName: String): NetworkSpecifier { - // Until R, there was no EthernetNetworkSpecifier, StringNetworkSpecifier was used instead - if (!isAtLeastS()) { - return makeNetworkSpecifierInternal("android.net.StringNetworkSpecifier", ifName) - } - // EthernetNetworkSpecifier is not part of the SDK in some branches using this utility - // TODO: replace with a direct call to the constructor - return makeNetworkSpecifierInternal("android.net.EthernetNetworkSpecifier", ifName) - } - - private fun makeNetworkSpecifierInternal(clazz: String, specifier: String): NetworkSpecifier { - // StringNetworkSpecifier was removed after R (and was hidden API before that) - return Class.forName(clazz) - .getConstructor(String::class.java).newInstance(specifier) as NetworkSpecifier - } -} diff --git a/common/testutils/devicetests/com/android/testutils/ConcurrentInterpreter.kt b/common/testutils/devicetests/com/android/testutils/ConcurrentInterpreter.kt deleted file mode 100644 index 9e72f4b4..00000000 --- a/common/testutils/devicetests/com/android/testutils/ConcurrentInterpreter.kt +++ /dev/null @@ -1,240 +0,0 @@ -package com.android.testutils - -import android.os.SystemClock -import java.util.concurrent.CyclicBarrier -import kotlin.test.assertEquals -import kotlin.test.assertFails -import kotlin.test.assertNull -import kotlin.test.assertTrue - -// The table contains pairs associating a regexp with the code to run. The statement is matched -// against each matcher in sequence and when a match is found the associated code is run, passing -// it the TrackRecord under test and the result of the regexp match. -typealias InterpretMatcher<T> = Pair<Regex, (ConcurrentInterpreter<T>, T, MatchResult) -> Any?> - -// The default unit of time for interpreted tests -const val INTERPRET_TIME_UNIT = 60L // ms - -/** - * A small interpreter for testing parallel code. - * - * The interpreter will read a list of lines consisting of "|"-separated statements, e.g. : - * sleep 2 ; unblock thread2 | wait thread2 time 2..5 - * sendMessage "x" | obtainMessage = "x" time 0..1 - * - * Each column runs in a different concurrent thread and all threads wait for each other in - * between lines. Each statement is split on ";" then matched with regular expressions in the - * instructionTable constant, which contains the code associated with each statement. The - * interpreter supports an object being passed to the interpretTestSpec() method to be passed - * in each lambda (think about the object under test), and an optional transform function to be - * executed on the object at the start of every thread. - * - * The time unit is defined in milliseconds by the interpretTimeUnit member, which has a default - * value but can be passed to the constructor. Whitespace is ignored. - * - * The interpretation table has to be passed as an argument. It's a table associating a regexp - * with the code that should execute, as a function taking three arguments : the interpreter, - * the regexp match, and the object. See the individual tests for the DSL of that test. - * Implementors for new interpreting languages are encouraged to look at the defaultInterpretTable - * constant below for an example of how to write an interpreting table. - * Some expressions already exist by default and can be used by all interpreters. Refer to - * getDefaultInstructions() below for a list and documentation. - */ -open class ConcurrentInterpreter<T>(localInterpretTable: List<InterpretMatcher<T>>) { - private val interpretTable: List<InterpretMatcher<T>> = - localInterpretTable + getDefaultInstructions() - // The last time the thread became blocked, with base System.currentTimeMillis(). This should - // be set immediately before any time the thread gets blocked. - internal val lastBlockedTime = ThreadLocal<Long>() - - // Split the line into multiple statements separated by ";" and execute them. Return whatever - // the last statement returned. - fun interpretMultiple(instr: String, r: T): Any? { - return instr.split(";").map { interpret(it.trim(), r) }.last() - } - - // Match the statement to a regex and interpret it. - fun interpret(instr: String, r: T): Any? { - val (matcher, code) = - interpretTable.find { instr matches it.first } ?: throw SyntaxException(instr) - val match = matcher.matchEntire(instr) ?: throw SyntaxException(instr) - return code(this, r, match) - } - - /** - * Spins as many threads as needed by the test spec and interpret each program concurrently. - * - * All threads wait on a CyclicBarrier after each line. - * |lineShift| says how many lines after the call the spec starts. This is used for error - * reporting. Unfortunately AFAICT there is no way to get the line of an argument rather - * than the line at which the expression starts. - * - * This method is mostly meant for implementations that extend the ConcurrentInterpreter - * class to add their own directives and instructions. These may need to operate on some - * data, which can be passed in |initial|. For example, an interpreter specialized in callbacks - * may want to pass the callback there. In some cases, it's necessary that each thread - * performs a transformation *after* it starts on that value before starting ; in this case, - * the transformation can be passed to |threadTransform|. The default is to return |initial| as - * is. Look at some existing child classes of this interpreter for some examples of how this - * can be used. - * - * @param spec The test spec, as a string of lines separated by pipes. - * @param initial An initial value passed to all threads. - * @param lineShift How many lines after the call the spec starts, for error reporting. - * @param threadTransform an optional transformation that each thread will apply to |initial| - */ - fun interpretTestSpec( - spec: String, - initial: T, - lineShift: Int = 0, - threadTransform: (T) -> T = { it } - ) { - // For nice stack traces - val callSite = getCallingMethod() - val lines = spec.trim().trim('\n').split("\n").map { it.split("|") } - // |lines| contains arrays of strings that make up the statements of a thread : in other - // words, it's an array that contains a list of statements for each column in the spec. - // E.g. if the string is """ - // a | b | c - // d | e | f - // """, then lines is [ [ "a", "b", "c" ], [ "d", "e", "f" ] ]. - val threadCount = lines[0].size - assertTrue(lines.all { it.size == threadCount }) - val threadInstructions = (0 until threadCount).map { i -> lines.map { it[i].trim() } } - // |threadInstructions| is a list where each element is the list of instructions for the - // thread at the index. In other words, it's just |lines| transposed. In the example - // above, it would be [ [ "a", "d" ], [ "b", "e" ], [ "c", "f" ] ] - // mapIndexed below will pass in |instructions| the list of instructions for this thread. - val barrier = CyclicBarrier(threadCount) - var crash: InterpretException? = null - threadInstructions.mapIndexed { threadIndex, instructions -> - Thread { - val threadLocal = threadTransform(initial) - lastBlockedTime.set(System.currentTimeMillis()) - barrier.await() - var lineNum = 0 - instructions.forEach { - if (null != crash) return@Thread - lineNum += 1 - try { - interpretMultiple(it, threadLocal) - } catch (e: Throwable) { - // If fail() or some exception was called, the thread will come here ; if - // the exception isn't caught the process will crash, which is not nice for - // testing. Instead, catch the exception, cancel other threads, and report - // nicely. Catch throwable because fail() is AssertionError, which inherits - // from Error. - crash = InterpretException(threadIndex, it, - callSite.lineNumber + lineNum + lineShift, - callSite.className, callSite.methodName, callSite.fileName, e) - } - lastBlockedTime.set(System.currentTimeMillis()) - barrier.await() - } - }.also { it.start() } - }.forEach { it.join() } - // If the test failed, crash with line number - crash?.let { throw it } - } - - // Helper to get the stack trace for a calling method - private fun getCallingStackTrace(): Array<StackTraceElement> { - try { - throw RuntimeException() - } catch (e: RuntimeException) { - return e.stackTrace - } - } - - // Find the calling method. This is the first method in the stack trace that is annotated - // with @Test. - fun getCallingMethod(): StackTraceElement { - val stackTrace = getCallingStackTrace() - return stackTrace.find { element -> - val clazz = Class.forName(element.className) - // Because the stack trace doesn't list the formal arguments, find all methods with - // this name and return this name if any of them is annotated with @Test. - clazz.declaredMethods - .filter { method -> method.name == element.methodName } - .any { method -> method.getAnnotation(org.junit.Test::class.java) != null } - } ?: stackTrace[3] - // If no method is annotated return the 4th one, because that's what it usually is : - // 0 is getCallingStackTrace, 1 is this method, 2 is ConcurrentInterpreter#interpretTestSpec - } -} - -/** - * Default instructions available to all interpreters. - * sleep(x) : sleeps for x time units and returns Unit ; sleep alone means sleep(1) - * EXPR = VALUE : asserts that EXPR equals VALUE. EXPR is interpreted. VALUE can either be the - * string "null" or an int. Returns Unit. - * EXPR time x..y : measures the time taken by EXPR and asserts it took at least x and at most - * y time units. - * EXPR // any text : comments are ignored. - * EXPR fails : checks that EXPR throws some exception. - */ -private fun <T> getDefaultInstructions() = listOf<InterpretMatcher<T>>( - // Interpret an empty line as doing nothing. - Regex("") to { _, _, _ -> null }, - // Ignore comments. - Regex("(.*)//.*") to { i, t, r -> i.interpret(r.strArg(1), t) }, - // Interpret "XXX time x..y" : run XXX and check it took at least x and not more than y - Regex("""(.*)\s*time\s*(\d+)\.\.(\d+)""") to { i, t, r -> - val lateStart = System.currentTimeMillis() - i.interpret(r.strArg(1), t) - val end = System.currentTimeMillis() - // There is uncertainty in measuring time. - // It takes some (small) time for the thread to even measure the time at which it - // starts interpreting the instruction. It is therefore possible that thread A sleeps for - // n milliseconds, and B expects to have waited for at least n milliseconds, but because - // B started measuring after 1ms or so, B thinks it didn't wait long enough. - // To avoid this, when the `time` instruction tests the instruction took at least X and - // at most Y, it tests X against a time measured since *before* the thread blocked but - // Y against a time measured as late as possible. This ensures that the timer is - // sufficiently lenient in both directions that there are no flaky measures. - val minTime = end - lateStart - val maxTime = end - i.lastBlockedTime.get()!! - - assertTrue(maxTime >= r.timeArg(2), - "Should have taken at least ${r.timeArg(2)} but took less than $maxTime") - assertTrue(minTime <= r.timeArg(3), - "Should have taken at most ${r.timeArg(3)} but took more than $minTime") - }, - // Interpret "XXX = YYY" : run XXX and assert its return value is equal to YYY. "null" supported - Regex("""(.*)\s*=\s*(null|\d+)""") to { i, t, r -> - i.interpret(r.strArg(1), t).also { - if ("null" == r.strArg(2)) assertNull(it) else assertEquals(r.intArg(2), it) - } - }, - // Interpret sleep. Optional argument for the count, in INTERPRET_TIME_UNIT units. - Regex("""sleep(\((\d+)\))?""") to { i, t, r -> - SystemClock.sleep(if (r.strArg(2).isEmpty()) INTERPRET_TIME_UNIT else r.timeArg(2)) - }, - Regex("""(.*)\s*fails""") to { i, t, r -> - assertFails { i.interpret(r.strArg(1), t) } - } -) - -class SyntaxException(msg: String, cause: Throwable? = null) : RuntimeException(msg, cause) -class InterpretException( - threadIndex: Int, - instr: String, - lineNum: Int, - className: String, - methodName: String, - fileName: String, - cause: Throwable -) : RuntimeException("Failure: $instr", cause) { - init { - stackTrace = arrayOf(StackTraceElement( - className, - "$methodName:thread$threadIndex", - fileName, - lineNum)) + super.getStackTrace() - } -} - -// Some small helpers to avoid to say the large ".groupValues[index].trim()" every time -fun MatchResult.strArg(index: Int) = this.groupValues[index].trim() -fun MatchResult.intArg(index: Int) = strArg(index).toInt() -fun MatchResult.timeArg(index: Int) = INTERPRET_TIME_UNIT * intArg(index) diff --git a/common/testutils/devicetests/com/android/testutils/ConnectUtil.kt b/common/testutils/devicetests/com/android/testutils/ConnectUtil.kt deleted file mode 100644 index 71f7877e..00000000 --- a/common/testutils/devicetests/com/android/testutils/ConnectUtil.kt +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.Manifest.permission -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.net.ConnectivityManager -import android.net.Network -import android.net.NetworkCapabilities.TRANSPORT_WIFI -import android.net.NetworkRequest -import android.net.wifi.ScanResult -import android.net.wifi.WifiConfiguration -import android.net.wifi.WifiManager -import android.os.ParcelFileDescriptor -import android.os.SystemClock -import android.util.Log -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation -import com.android.testutils.RecorderCallback.CallbackEntry -import java.util.concurrent.CompletableFuture -import java.util.concurrent.TimeUnit -import kotlin.test.assertNotNull -import kotlin.test.assertTrue -import kotlin.test.fail - -private const val MAX_WIFI_CONNECT_RETRIES = 10 -private const val WIFI_CONNECT_INTERVAL_MS = 500L -private const val WIFI_CONNECT_TIMEOUT_MS = 30_000L - -// Constants used by WifiManager.ActionListener#onFailure. Although onFailure is SystemApi, -// the error code constants are not (b/204277752) -private const val WIFI_ERROR_IN_PROGRESS = 1 -private const val WIFI_ERROR_BUSY = 2 - -class ConnectUtil(private val context: Context) { - private val TAG = ConnectUtil::class.java.simpleName - - private val cm = context.getSystemService(ConnectivityManager::class.java) - ?: fail("Could not find ConnectivityManager") - private val wifiManager = context.getSystemService(WifiManager::class.java) - ?: fail("Could not find WifiManager") - - fun ensureWifiConnected(): Network { - val callback = TestableNetworkCallback() - cm.registerNetworkCallback(NetworkRequest.Builder() - .addTransportType(TRANSPORT_WIFI) - .build(), callback) - - try { - val connInfo = wifiManager.connectionInfo - Log.d(TAG, "connInfo=" + connInfo) - if (connInfo == null || connInfo.networkId == -1) { - clearWifiBlocklist() - val pfd = getInstrumentation().uiAutomation.executeShellCommand("svc wifi enable") - // Read the output stream to ensure the command has completed - ParcelFileDescriptor.AutoCloseInputStream(pfd).use { it.readBytes() } - val config = getOrCreateWifiConfiguration() - connectToWifiConfig(config) - } - val cb = callback.poll(WIFI_CONNECT_TIMEOUT_MS) { it is CallbackEntry.Available } - assertNotNull(cb, "Could not connect to a wifi access point within " + - "$WIFI_CONNECT_TIMEOUT_MS ms. Check that the test device has a wifi network " + - "configured, and that the test access point is functioning properly.") - return cb.network - } finally { - cm.unregisterNetworkCallback(callback) - } - } - - private fun connectToWifiConfig(config: WifiConfiguration) { - repeat(MAX_WIFI_CONNECT_RETRIES) { - val error = runAsShell(permission.NETWORK_SETTINGS) { - val listener = ConnectWifiListener() - wifiManager.connect(config, listener) - listener.connectFuture.get(WIFI_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS) - } ?: return // Connect succeeded - - // Only retry for IN_PROGRESS and BUSY - if (error != WIFI_ERROR_IN_PROGRESS && error != WIFI_ERROR_BUSY) { - fail("Failed to connect to " + config.SSID + ": " + error) - } - Log.w(TAG, "connect failed with $error; waiting before retry") - SystemClock.sleep(WIFI_CONNECT_INTERVAL_MS) - } - fail("Failed to connect to ${config.SSID} after $MAX_WIFI_CONNECT_RETRIES retries") - } - - private class ConnectWifiListener : WifiManager.ActionListener { - /** - * Future completed when the connect process ends. Provides the error code or null if none. - */ - val connectFuture = CompletableFuture<Int?>() - override fun onSuccess() { - connectFuture.complete(null) - } - - override fun onFailure(reason: Int) { - connectFuture.complete(reason) - } - } - - private fun getOrCreateWifiConfiguration(): WifiConfiguration { - val configs = runAsShell(permission.NETWORK_SETTINGS) { - wifiManager.getConfiguredNetworks() - } - // If no network is configured, add a config for virtual access points if applicable - if (configs.size == 0) { - val scanResults = getWifiScanResults() - val virtualConfig = maybeConfigureVirtualNetwork(scanResults) - assertNotNull(virtualConfig, "The device has no configured wifi network") - return virtualConfig - } - // No need to add a configuration: there is already one. - if (configs.size > 1) { - // For convenience in case of local testing on devices with multiple saved configs, - // prefer the first configuration that is in range. - // In actual tests, there should only be one configuration, and it should be usable as - // assumed by WifiManagerTest.testConnect. - Log.w(TAG, "Multiple wifi configurations found: " + - configs.joinToString(", ") { it.SSID }) - val scanResultsList = getWifiScanResults() - Log.i(TAG, "Scan results: " + scanResultsList.joinToString(", ") { - "${it.SSID} (${it.level})" - }) - - val scanResults = scanResultsList.map { "\"${it.SSID}\"" }.toSet() - return configs.firstOrNull { scanResults.contains(it.SSID) } ?: configs[0] - } - return configs[0] - } - - private fun getWifiScanResults(): List<ScanResult> { - val scanResultsFuture = CompletableFuture<List<ScanResult>>() - runAsShell(permission.NETWORK_SETTINGS) { - val receiver: BroadcastReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - scanResultsFuture.complete(wifiManager.scanResults) - } - } - context.registerReceiver(receiver, - IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) - wifiManager.startScan() - } - return try { - scanResultsFuture.get(WIFI_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS) - } catch (e: Exception) { - throw AssertionError("Wifi scan results not received within timeout", e) - } - } - - /** - * If a virtual wifi network is detected, add a configuration for that network. - * TODO(b/158150376): have the test infrastructure add virtual wifi networks when appropriate. - */ - private fun maybeConfigureVirtualNetwork(scanResults: List<ScanResult>): WifiConfiguration? { - // Virtual wifi networks used on the emulator and cloud testing infrastructure - val virtualSsids = listOf("VirtWifi", "AndroidWifi") - Log.d(TAG, "Wifi scan results: $scanResults") - val virtualScanResult = scanResults.firstOrNull { virtualSsids.contains(it.SSID) } - ?: return null - - // Only add the virtual configuration if the virtual AP is detected in scans - val virtualConfig = WifiConfiguration() - // ASCII SSIDs need to be surrounded by double quotes - virtualConfig.SSID = "\"${virtualScanResult.SSID}\"" - virtualConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE) - runAsShell(permission.NETWORK_SETTINGS) { - val networkId = wifiManager.addNetwork(virtualConfig) - assertTrue(networkId >= 0) - assertTrue(wifiManager.enableNetwork(networkId, false /* attemptConnect */)) - } - return virtualConfig - } - - /** - * Re-enable wifi networks that were blocked, typically because no internet connection was - * detected the last time they were connected. This is necessary to make sure wifi can reconnect - * to them. - */ - private fun clearWifiBlocklist() { - runAsShell(permission.NETWORK_SETTINGS, permission.ACCESS_WIFI_STATE) { - for (cfg in wifiManager.configuredNetworks) { - assertTrue(wifiManager.enableNetwork(cfg.networkId, false /* attemptConnect */)) - } - } - } -} diff --git a/common/testutils/devicetests/com/android/testutils/ContextUtils.kt b/common/testutils/devicetests/com/android/testutils/ContextUtils.kt deleted file mode 100644 index 936b5682..00000000 --- a/common/testutils/devicetests/com/android/testutils/ContextUtils.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:JvmName("ContextUtils") - -package com.android.testutils - -import android.content.Context -import android.os.UserHandle -import org.mockito.AdditionalAnswers.delegatesTo -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.Mockito.doAnswer -import org.mockito.Mockito.doReturn -import org.mockito.Mockito.mock -import java.util.function.BiConsumer - -// Helper function so that Java doesn't have to pass a method that returns Unit -fun mockContextAsUser(context: Context, functor: BiConsumer<Context, UserHandle>? = null) = - mockContextAsUser(context) { c, h -> functor?.accept(c, h) } - -/** - * Return a context with assigned user and delegate to original context. - * - * @param context the mock context to set up createContextAsUser on. After this function - * is called, client code can call createContextAsUser and expect a context that - * will return the correct user and userId. - * - * @param functor additional code to run on the created context-as-user instances, for example to - * set up further mocks on these contexts. - */ -fun mockContextAsUser(context: Context, functor: ((Context, UserHandle) -> Unit)? = null) { - doAnswer { invocation -> - val asUserContext = mock(Context::class.java, delegatesTo<Context>(context)) - val user = invocation.arguments[0] as UserHandle - val userId = user.identifier - doReturn(user).`when`(asUserContext).user - doReturn(userId).`when`(asUserContext).userId - functor?.let { it(asUserContext, user) } - asUserContext - }.`when`(context).createContextAsUser(any(UserHandle::class.java), anyInt() /* flags */) -} - -/** - * Helper function to mock the desired system service. - * - * @param context the mock context to set up the getSystemService and getSystemServiceName. - * @param clazz the system service class that intents to mock. - * @param service the system service name that intents to mock. - */ -fun <T> mockService(context: Context, clazz: Class<T>, name: String, service: T) { - doReturn(service).`when`(context).getSystemService(name) - doReturn(name).`when`(context).getSystemServiceName(clazz) -}
\ No newline at end of file diff --git a/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt b/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt deleted file mode 100644 index 35f22b9e..00000000 --- a/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.os.Build -import androidx.test.InstrumentationRegistry -import com.android.modules.utils.build.UnboundedSdkLevel -import java.util.regex.Pattern -import org.junit.Assume.assumeTrue -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement - -@Deprecated("Use Build.VERSION_CODES", ReplaceWith("Build.VERSION_CODES.S_V2")) -const val SC_V2 = Build.VERSION_CODES.S_V2 - -private val MAX_TARGET_SDK_ANNOTATION_RE = Pattern.compile("MaxTargetSdk([0-9]+)$") -private val targetSdk = InstrumentationRegistry.getContext().applicationInfo.targetSdkVersion - -private fun isDevSdkInRange(minExclusive: String?, maxInclusive: String?): Boolean { - return (minExclusive == null || !isAtMost(minExclusive)) && - (maxInclusive == null || isAtMost(maxInclusive)) -} - -private fun isAtMost(sdkVersionOrCodename: String): Boolean { - // UnboundedSdkLevel does not support builds < Q, and may stop supporting Q as well since it - // is intended for mainline modules that are now R+. - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { - // Assume that any codename passed as argument from current code is a more recent build than - // Q: this util did not exist before Q, and codenames are only used before the corresponding - // build is finalized. This util could list 28 older codenames to check against (as per - // ro.build.version.known_codenames in more recent builds), but this does not seem valuable. - val intVersion = sdkVersionOrCodename.toIntOrNull() ?: return true - return Build.VERSION.SDK_INT <= intVersion - } - return UnboundedSdkLevel.isAtMost(sdkVersionOrCodename) -} - -/** - * Returns true if the development SDK version of the device is in the provided annotation range. - * - * If the device is not using a release SDK, the development SDK differs from - * [Build.VERSION.SDK_INT], and is indicated by the device codenames; see [UnboundedSdkLevel]. - */ -fun isDevSdkInRange( - ignoreUpTo: DevSdkIgnoreRule.IgnoreUpTo?, - ignoreAfter: DevSdkIgnoreRule.IgnoreAfter? -): Boolean { - val minExclusive = - if (ignoreUpTo?.value == 0) ignoreUpTo.codename - else ignoreUpTo?.value?.toString() - val maxInclusive = - if (ignoreAfter?.value == 0) ignoreAfter.codename - else ignoreAfter?.value?.toString() - return isDevSdkInRange(minExclusive, maxInclusive) -} - -private fun getMaxTargetSdk(description: Description): Int? { - return description.annotations.firstNotNullOfOrNull { - MAX_TARGET_SDK_ANNOTATION_RE.matcher(it.annotationClass.simpleName).let { m -> - if (m.find()) m.group(1).toIntOrNull() else null - } - } -} - -/** - * A test rule to ignore tests based on the development SDK level. - * - * If the device is not using a release SDK, the development SDK is considered to be higher than - * [Build.VERSION.SDK_INT]. - * - * @param ignoreClassUpTo Skip all tests in the class if the device dev SDK is <= this codename or - * SDK level. - * @param ignoreClassAfter Skip all tests in the class if the device dev SDK is > this codename or - * SDK level. - */ -class DevSdkIgnoreRule @JvmOverloads constructor( - private val ignoreClassUpTo: String? = null, - private val ignoreClassAfter: String? = null -) : TestRule { - /** - * @param ignoreClassUpTo Skip all tests in the class if the device dev SDK is <= this value. - * @param ignoreClassAfter Skip all tests in the class if the device dev SDK is > this value. - */ - @JvmOverloads - constructor(ignoreClassUpTo: Int?, ignoreClassAfter: Int? = null) : this( - ignoreClassUpTo?.toString(), ignoreClassAfter?.toString()) - - override fun apply(base: Statement, description: Description): Statement { - return IgnoreBySdkStatement(base, description) - } - - /** - * Ignore the test for any development SDK that is strictly after [value]. - * - * If the device is not using a release SDK, the development SDK is considered to be higher - * than [Build.VERSION.SDK_INT]. - */ - annotation class IgnoreAfter(val value: Int = 0, val codename: String = "") - - /** - * Ignore the test for any development SDK that lower than or equal to [value]. - * - * If the device is not using a release SDK, the development SDK is considered to be higher - * than [Build.VERSION.SDK_INT]. - */ - annotation class IgnoreUpTo(val value: Int = 0, val codename: String = "") - - private inner class IgnoreBySdkStatement( - private val base: Statement, - private val description: Description - ) : Statement() { - override fun evaluate() { - val ignoreAfter = description.getAnnotation(IgnoreAfter::class.java) - val ignoreUpTo = description.getAnnotation(IgnoreUpTo::class.java) - - val devSdkMessage = "Skipping test for build ${Build.VERSION.CODENAME} " + - "with SDK ${Build.VERSION.SDK_INT}" - assumeTrue(devSdkMessage, isDevSdkInRange(ignoreClassUpTo, ignoreClassAfter)) - assumeTrue(devSdkMessage, isDevSdkInRange(ignoreUpTo, ignoreAfter)) - - val maxTargetSdk = getMaxTargetSdk(description) - if (maxTargetSdk != null) { - assumeTrue("Skipping test, target SDK $targetSdk greater than $maxTargetSdk", - targetSdk <= maxTargetSdk) - } - base.evaluate() - } - } -} diff --git a/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt b/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt deleted file mode 100644 index 2e73666b..00000000 --- a/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter -import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo -import org.junit.runner.Description -import org.junit.runner.Runner -import org.junit.runner.manipulation.Filter -import org.junit.runner.manipulation.Filterable -import org.junit.runner.manipulation.NoTestsRemainException -import org.junit.runner.manipulation.Sortable -import org.junit.runner.manipulation.Sorter -import org.junit.runner.notification.RunNotifier -import kotlin.jvm.Throws - -/** - * A runner that can skip tests based on the development SDK as defined in [DevSdkIgnoreRule]. - * - * Generally [DevSdkIgnoreRule] should be used for that purpose (using rules is preferable over - * replacing the test runner), however JUnit runners inspect all methods in the test class before - * processing test rules. This may cause issues if the test methods are referencing classes that do - * not exist on the SDK of the device the test is run on. - * - * This runner inspects [IgnoreAfter] and [IgnoreUpTo] annotations on the test class, and will skip - * the whole class if they do not match the development SDK as defined in [DevSdkIgnoreRule]. - * Otherwise, it will delegate to [AndroidJUnit4] to run the test as usual. - * - * Example usage: - * - * @RunWith(DevSdkIgnoreRunner::class) - * @IgnoreUpTo(Build.VERSION_CODES.Q) - * class MyTestClass { ... } - */ -class DevSdkIgnoreRunner(private val klass: Class<*>) : Runner(), Filterable, Sortable { - private val baseRunner = klass.let { - val ignoreAfter = it.getAnnotation(IgnoreAfter::class.java) - val ignoreUpTo = it.getAnnotation(IgnoreUpTo::class.java) - - if (isDevSdkInRange(ignoreUpTo, ignoreAfter)) AndroidJUnit4(klass) else null - } - - override fun run(notifier: RunNotifier) { - if (baseRunner != null) { - baseRunner.run(notifier) - return - } - - // Report a single, skipped placeholder test for this class, as the class is expected to - // report results when run. In practice runners that apply the Filterable implementation - // would see a NoTestsRemainException and not call the run method. - notifier.fireTestIgnored( - Description.createTestDescription(klass, "skippedClassForDevSdkMismatch")) - } - - override fun getDescription(): Description { - return baseRunner?.description ?: Description.createSuiteDescription(klass) - } - - /** - * Get the test count before applying the [Filterable] implementation. - */ - override fun testCount(): Int { - // When ignoring the tests, a skipped placeholder test is reported, so test count is 1. - return baseRunner?.testCount() ?: 1 - } - - @Throws(NoTestsRemainException::class) - override fun filter(filter: Filter?) { - baseRunner?.filter(filter) ?: throw NoTestsRemainException() - } - - override fun sort(sorter: Sorter?) { - baseRunner?.sort(sorter) - } -}
\ No newline at end of file diff --git a/common/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt b/common/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt deleted file mode 100644 index 3d98cc38..00000000 --- a/common/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.Manifest.permission.READ_DEVICE_CONFIG -import android.Manifest.permission.WRITE_DEVICE_CONFIG -import android.provider.DeviceConfig -import android.util.Log -import com.android.modules.utils.build.SdkLevel -import com.android.testutils.FunctionalUtils.ThrowingRunnable -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement -import java.util.concurrent.CompletableFuture -import java.util.concurrent.Executor -import java.util.concurrent.TimeUnit - -private val TAG = DeviceConfigRule::class.simpleName - -private const val TIMEOUT_MS = 20_000L - -/** - * A [TestRule] that helps set [DeviceConfig] for tests and clean up the test configuration - * automatically on teardown. - * - * The rule can also optionally retry tests when they fail following an external change of - * DeviceConfig before S; this typically happens because device config flags are synced while the - * test is running, and DisableConfigSyncTargetPreparer is only usable starting from S. - * - * @param retryCountBeforeSIfConfigChanged if > 0, when the test fails before S, check if - * the configs that were set through this rule were changed, and retry the test - * up to the specified number of times if yes. - */ -class DeviceConfigRule @JvmOverloads constructor( - val retryCountBeforeSIfConfigChanged: Int = 0 -) : TestRule { - // Maps (namespace, key) -> value - private val originalConfig = mutableMapOf<Pair<String, String>, String?>() - private val usedConfig = mutableMapOf<Pair<String, String>, String?>() - - /** - * Actions to be run after cleanup of the config, for the current test only. - */ - private val currentTestCleanupActions = mutableListOf<ThrowingRunnable>() - - override fun apply(base: Statement, description: Description): Statement { - return TestValidationUrlStatement(base, description) - } - - private inner class TestValidationUrlStatement( - private val base: Statement, - private val description: Description - ) : Statement() { - override fun evaluate() { - var retryCount = if (SdkLevel.isAtLeastS()) 1 else retryCountBeforeSIfConfigChanged + 1 - while (retryCount > 0) { - retryCount-- - tryTest { - base.evaluate() - // Can't use break/return out of a loop here because this is a tryTest lambda, - // so set retryCount to exit instead - retryCount = 0 - }.catch<Throwable> { e -> // junit AssertionFailedError does not extend Exception - if (retryCount == 0) throw e - usedConfig.forEach { (key, value) -> - val currentValue = runAsShell(READ_DEVICE_CONFIG) { - DeviceConfig.getProperty(key.first, key.second) - } - if (currentValue != value) { - Log.w(TAG, "Test failed with unexpected device config change, retrying") - return@catch - } - } - throw e - } cleanupStep { - runAsShell(WRITE_DEVICE_CONFIG) { - originalConfig.forEach { (key, value) -> - DeviceConfig.setProperty( - key.first, key.second, value, false /* makeDefault */) - } - } - } cleanupStep { - originalConfig.clear() - usedConfig.clear() - } cleanup { - // Fold all cleanup actions into cleanup steps of an empty tryTest, so they are - // all run even if exceptions are thrown, and exceptions are reported properly. - currentTestCleanupActions.fold(tryTest { }) { - tryBlock, action -> tryBlock.cleanupStep { action.run() } - }.cleanup { - currentTestCleanupActions.clear() - } - } - } - } - } - - /** - * Set a configuration key/value. After the test case ends, it will be restored to the value it - * had when this method was first called. - */ - fun setConfig(namespace: String, key: String, value: String?): String? { - Log.i(TAG, "Setting config \"$key\" to \"$value\"") - val readWritePermissions = arrayOf(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) - - val keyPair = Pair(namespace, key) - val existingValue = runAsShell(*readWritePermissions) { - DeviceConfig.getProperty(namespace, key) - } - if (!originalConfig.containsKey(keyPair)) { - originalConfig[keyPair] = existingValue - } - usedConfig[keyPair] = value - if (existingValue == value) { - // Already the correct value. There may be a race if a change is already in flight, - // but if multiple threads update the config there is no way to fix that anyway. - Log.i(TAG, "\"$key\" already had value \"$value\"") - return value - } - - val future = CompletableFuture<String>() - val listener = DeviceConfig.OnPropertiesChangedListener { - // The listener receives updates for any change to any key, so don't react to - // changes that do not affect the relevant key - if (!it.keyset.contains(key)) return@OnPropertiesChangedListener - // "null" means absent in DeviceConfig : there is no such thing as a present but - // null value, so the following works even if |value| is null. - if (it.getString(key, null) == value) { - future.complete(value) - } - } - - return tryTest { - runAsShell(*readWritePermissions) { - DeviceConfig.addOnPropertiesChangedListener( - DeviceConfig.NAMESPACE_CONNECTIVITY, - inlineExecutor, - listener) - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_CONNECTIVITY, - key, - value, - false /* makeDefault */) - // Don't drop the permission until the config is applied, just in case - future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) - }.also { - Log.i(TAG, "Config \"$key\" successfully set to \"$value\"") - } - } cleanup { - DeviceConfig.removeOnPropertiesChangedListener(listener) - } - } - - private val inlineExecutor get() = Executor { r -> r.run() } - - /** - * Add an action to be run after config cleanup when the current test case ends. - */ - fun runAfterNextCleanup(action: ThrowingRunnable) { - currentTestCleanupActions.add(action) - } -} diff --git a/common/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java b/common/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java deleted file mode 100644 index ce55fdc1..00000000 --- a/common/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils; - -import android.os.VintfRuntimeInfo; -import android.text.TextUtils; -import android.util.Pair; - -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Utilities for device information. - */ -public class DeviceInfoUtils { - /** - * Class for a three-part kernel version number. - */ - public static class KVersion { - public final int major; - public final int minor; - public final int sub; - - public KVersion(int major, int minor, int sub) { - this.major = major; - this.minor = minor; - this.sub = sub; - } - - /** - * Compares with other version numerically. - * - * @param other the other version to compare - * @return the value 0 if this == other; - * a value less than 0 if this < other and - * a value greater than 0 if this > other. - */ - public int compareTo(final KVersion other) { - int res = Integer.compare(this.major, other.major); - if (res == 0) { - res = Integer.compare(this.minor, other.minor); - } - if (res == 0) { - res = Integer.compare(this.sub, other.sub); - } - return res; - } - - /** - * At least satisfied with the given version. - * - * @param from the start version to compare - * @return return true if this version is at least satisfied with the given version. - * otherwise, return false. - */ - public boolean isAtLeast(final KVersion from) { - return compareTo(from) >= 0; - } - - /** - * Falls within the given range [from, to). - * - * @param from the start version to compare - * @param to the end version to compare - * @return return true if this version falls within the given range. - * otherwise, return false. - */ - public boolean isInRange(final KVersion from, final KVersion to) { - return isAtLeast(from) && !isAtLeast(to); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof KVersion)) return false; - KVersion that = (KVersion) o; - return this.major == that.major - && this.minor == that.minor - && this.sub == that.sub; - } - }; - - /** - * Get a two-part kernel version number (major and minor) from a given string. - * - * TODO: use class KVersion. - */ - private static Pair<Integer, Integer> getMajorMinorVersion(String version) { - // Only gets major and minor number of the version string. - final Pattern versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*"); - final Matcher m = versionPattern.matcher(version); - if (m.matches()) { - final int major = Integer.parseInt(m.group(1)); - final int minor = TextUtils.isEmpty(m.group(3)) ? 0 : Integer.parseInt(m.group(3)); - return new Pair<>(major, minor); - } else { - return new Pair<>(0, 0); - } - } - - /** - * Compares two version strings numerically. Compare only major and minor number of the - * version string. The version comparison uses #Integer.compare. Possible version - * 5, 5.10, 5-beta1, 4.8-RC1, 4.7.10.10 and so on. - * - * @param s1 the first version string to compare - * @param s2 the second version string to compare - * @return the value 0 if s1 == s2; - * a value less than 0 if s1 < s2 and - * a value greater than 0 if s1 > s2. - * - * TODO: use class KVersion. - */ - public static int compareMajorMinorVersion(final String s1, final String s2) { - final Pair<Integer, Integer> v1 = getMajorMinorVersion(s1); - final Pair<Integer, Integer> v2 = getMajorMinorVersion(s2); - - if (Objects.equals(v1.first, v2.first)) { - return Integer.compare(v1.second, v2.second); - } else { - return Integer.compare(v1.first, v2.first); - } - } - - /** - * Get a three-part kernel version number (major, minor and subminor) from a given string. - * Any version string must at least have major and minor number. If the subminor number can't - * be parsed from string. Assign zero as subminor number. Invalid version is treated as - * version 0.0.0. - */ - public static KVersion getMajorMinorSubminorVersion(final String version) { - // The kernel version is a three-part version number (major, minor and subminor). Get - // the three-part version numbers and discard the remaining stuff if any. - // For example: - // 4.19.220-g500ede0aed22-ab8272303 --> 4.19.220 - // 5.17-rc6-g52099515ca00-ab8032400 --> 5.17.0 - final Pattern versionPattern = Pattern.compile("^(\\d+)\\.(\\d+)(\\.(\\d+))?.*"); - final Matcher m = versionPattern.matcher(version); - if (m.matches()) { - final int major = Integer.parseInt(m.group(1)); - final int minor = Integer.parseInt(m.group(2)); - final int sub = TextUtils.isEmpty(m.group(4)) ? 0 : Integer.parseInt(m.group(4)); - return new KVersion(major, minor, sub); - } else { - return new KVersion(0, 0, 0); - } - } - - /** - * Check if the current kernel version is at least satisfied with the given version. - * - * @param version the start version to compare - * @return return true if the current version is at least satisfied with the given version. - * otherwise, return false. - */ - public static boolean isKernelVersionAtLeast(final String version) { - final String kernelVersion = VintfRuntimeInfo.getKernelVersion(); - final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion); - final KVersion from = DeviceInfoUtils.getMajorMinorSubminorVersion(version); - return current.isAtLeast(from); - } -} diff --git a/common/testutils/devicetests/com/android/testutils/DnsAnswerProvider.kt b/common/testutils/devicetests/com/android/testutils/DnsAnswerProvider.kt deleted file mode 100644 index 6a804bf9..00000000 --- a/common/testutils/devicetests/com/android/testutils/DnsAnswerProvider.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.net.DnsResolver.CLASS_IN -import com.android.net.module.util.DnsPacket -import com.android.net.module.util.DnsPacket.ANSECTION -import java.net.InetAddress -import java.util.concurrent.ConcurrentHashMap - -const val DEFAULT_TTL_S = 5L - -/** - * Helper class to store the mapping of DNS queries. - * - * DnsAnswerProvider is built atop a ConcurrentHashMap and as such it provides the same - * guarantees as ConcurrentHashMap between writing and reading elements. Specifically : - * - Setting an answer happens-before reading the same answer. - * - Callers can read and write concurrently from DnsAnswerProvider and expect no - * ConcurrentModificationException. - * Freshness of the answers depends on ordering of the threads ; if callers need a - * freshness guarantee, they need to provide the happens-before relationship from a - * write that they want to observe to the read that they need to be observed. - */ -class DnsAnswerProvider { - private val mDnsKeyToRecords = ConcurrentHashMap<String, List<DnsPacket.DnsRecord>>() - - /** - * Get answer for the specified hostname. - * - * @param query the target hostname. - * @param type type of record, could be A or AAAA. - * - * @return list of [DnsPacket.DnsRecord] associated to the query. Empty if no record matches. - */ - fun getAnswer(query: String, type: Int) = mDnsKeyToRecords[query] - .orEmpty().filter { it.nsType == type } - - /** Set answer for the specified {@code query}. - * - * @param query the target hostname - * @param addresses [List<InetAddress>] which could be used to generate multiple A or AAAA - * RRs with the corresponding addresses. - */ - fun setAnswer(query: String, hosts: List<InetAddress>) = mDnsKeyToRecords.put(query, hosts.map { - DnsPacket.DnsRecord.makeAOrAAAARecord(ANSECTION, query, CLASS_IN, DEFAULT_TTL_S, it) - }) - - fun clearAnswer(query: String) = mDnsKeyToRecords.remove(query) - fun clearAll() = mDnsKeyToRecords.clear() -}
\ No newline at end of file diff --git a/common/testutils/devicetests/com/android/testutils/DumpTestUtils.java b/common/testutils/devicetests/com/android/testutils/DumpTestUtils.java deleted file mode 100644 index d1037488..00000000 --- a/common/testutils/devicetests/com/android/testutils/DumpTestUtils.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils; - -import static com.android.testutils.TestPermissionUtil.runAsShell; - -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import android.os.IBinder; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.system.ErrnoException; -import android.system.Os; - -import libcore.io.IoUtils; -import libcore.io.Streams; - -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Utilities for testing output of service dumps. - */ -public class DumpTestUtils { - - private static String dumpService(String serviceName, boolean adoptPermission, String... args) - throws RemoteException, InterruptedException, ErrnoException { - final IBinder ib = ServiceManager.getService(serviceName); - FileDescriptor[] pipe = Os.pipe(); - - // Start a thread to read the dump output, or dump might block if it fills the pipe. - final CountDownLatch latch = new CountDownLatch(1); - AtomicReference<String> output = new AtomicReference<>(); - // Used to send exceptions back to the main thread to ensure that the test fails cleanly. - AtomicReference<Exception> exception = new AtomicReference<>(); - new Thread(() -> { - try { - output.set(Streams.readFully( - new InputStreamReader(new FileInputStream(pipe[0]), - StandardCharsets.UTF_8))); - latch.countDown(); - } catch (Exception e) { - exception.set(e); - latch.countDown(); - } - }).start(); - - final int timeoutMs = 5_000; - final String what = "service '" + serviceName + "' with args: " + Arrays.toString(args); - try { - if (adoptPermission) { - runAsShell(android.Manifest.permission.DUMP, () -> ib.dump(pipe[1], args)); - } else { - ib.dump(pipe[1], args); - } - IoUtils.closeQuietly(pipe[1]); - assertTrue("Dump of " + what + " timed out after " + timeoutMs + "ms", - latch.await(timeoutMs, TimeUnit.MILLISECONDS)); - } finally { - // Closing the fds will terminate the thread if it's blocked on read. - IoUtils.closeQuietly(pipe[0]); - if (pipe[1].valid()) IoUtils.closeQuietly(pipe[1]); - } - if (exception.get() != null) { - fail("Exception dumping " + what + ": " + exception.get()); - } - return output.get(); - } - - /** - * Dumps the specified service and returns a string. Sends a dump IPC to the given service - * with the specified args and a pipe, then reads from the pipe in a separate thread. - * The current process must already have the DUMP permission. - * - * @param serviceName the service to dump. - * @param args the arguments to pass to the dump function. - * @return The dump text. - * @throws RemoteException dumping the service failed. - * @throws InterruptedException the dump timed out. - * @throws ErrnoException opening or closing the pipe for the dump failed. - */ - public static String dumpService(String serviceName, String... args) - throws RemoteException, InterruptedException, ErrnoException { - return dumpService(serviceName, false, args); - } - - /** - * Dumps the specified service and returns a string. Sends a dump IPC to the given service - * with the specified args and a pipe, then reads from the pipe in a separate thread. - * Adopts the {@code DUMP} permission via {@code adoptShellPermissionIdentity} and then releases - * it. This method should not be used if the caller already has the shell permission identity. - * TODO: when Q and R are no longer supported, use - * {@link android.app.UiAutomation#getAdoptedShellPermissions} to automatically acquire the - * shell permission if the caller does not already have it. - * - * @param serviceName the service to dump. - * @param args the arguments to pass to the dump function. - * @return The dump text. - * @throws RemoteException dumping the service failed. - * @throws InterruptedException the dump timed out. - * @throws ErrnoException opening or closing the pipe for the dump failed. - */ - public static String dumpServiceWithShellPermission(String serviceName, String... args) - throws RemoteException, InterruptedException, ErrnoException { - return dumpService(serviceName, true, args); - } -} diff --git a/common/testutils/devicetests/com/android/testutils/FakeDns.kt b/common/testutils/devicetests/com/android/testutils/FakeDns.kt deleted file mode 100644 index 1f82a35d..00000000 --- a/common/testutils/devicetests/com/android/testutils/FakeDns.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.net.DnsResolver -import android.net.InetAddresses -import android.os.Looper -import android.os.Handler -import com.android.internal.annotations.GuardedBy -import java.net.InetAddress -import java.util.concurrent.Executor -import org.mockito.invocation.InvocationOnMock -import org.mockito.Mockito.any -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.doAnswer - -const val TYPE_UNSPECIFIED = -1 -// TODO: Integrate with NetworkMonitorTest. -class FakeDns(val mockResolver: DnsResolver) { - class DnsEntry(val hostname: String, val type: Int, val addresses: List<InetAddress>) { - fun match(host: String, type: Int) = hostname.equals(host) && type == type - } - - @GuardedBy("answers") - val answers = ArrayList<DnsEntry>() - - fun getAnswer(hostname: String, type: Int): DnsEntry? = synchronized(answers) { - return answers.firstOrNull { it.match(hostname, type) } - } - - fun setAnswer(hostname: String, answer: Array<String>, type: Int) = synchronized(answers) { - val ans = DnsEntry(hostname, type, generateAnswer(answer)) - // Replace or remove the existing one. - when (val index = answers.indexOfFirst { it.match(hostname, type) }) { - -1 -> answers.add(ans) - else -> answers[index] = ans - } - } - - private fun generateAnswer(answer: Array<String>) = - answer.filterNotNull().map { InetAddresses.parseNumericAddress(it) } - - fun startMocking() { - // Mock DnsResolver.query() w/o type - doAnswer { - mockAnswer(it, 1, -1, 3, 5) - }.`when`(mockResolver).query(any() /* network */, any() /* domain */, anyInt() /* flags */, - any() /* executor */, any() /* cancellationSignal */, any() /*callback*/) - // Mock DnsResolver.query() w/ type - doAnswer { - mockAnswer(it, 1, 2, 4, 6) - }.`when`(mockResolver).query(any() /* network */, any() /* domain */, anyInt() /* nsType */, - anyInt() /* flags */, any() /* executor */, any() /* cancellationSignal */, - any() /*callback*/) - } - - private fun mockAnswer( - it: InvocationOnMock, - posHos: Int, - posType: Int, - posExecutor: Int, - posCallback: Int - ) { - val hostname = it.arguments[posHos] as String - val executor = it.arguments[posExecutor] as Executor - val callback = it.arguments[posCallback] as DnsResolver.Callback<List<InetAddress>> - var type = if (posType != -1) it.arguments[posType] as Int else TYPE_UNSPECIFIED - val answer = getAnswer(hostname, type) - - if (answer != null && !answer.addresses.isNullOrEmpty()) { - Handler(Looper.getMainLooper()).post({ executor.execute({ - callback.onAnswer(answer.addresses, 0); }) }) - } - } - - /** Clears all entries. */ - fun clearAll() = synchronized(answers) { - answers.clear() - } -} diff --git a/common/testutils/devicetests/com/android/testutils/HandlerUtils.kt b/common/testutils/devicetests/com/android/testutils/HandlerUtils.kt deleted file mode 100644 index f00ca116..00000000 --- a/common/testutils/devicetests/com/android/testutils/HandlerUtils.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:JvmName("HandlerUtils") - -package com.android.testutils - -import android.os.ConditionVariable -import android.os.Handler -import android.os.HandlerThread -import android.util.Log -import com.android.testutils.FunctionalUtils.ThrowingRunnable -import com.android.testutils.FunctionalUtils.ThrowingSupplier -import java.lang.Exception -import java.util.concurrent.Executor -import kotlin.test.fail - -private const val TAG = "HandlerUtils" - -/** - * Block until the specified Handler or HandlerThread becomes idle, or until timeoutMs has passed. - */ -fun HandlerThread.waitForIdle(timeoutMs: Int) = threadHandler.waitForIdle(timeoutMs.toLong()) -fun HandlerThread.waitForIdle(timeoutMs: Long) = threadHandler.waitForIdle(timeoutMs) -fun Handler.waitForIdle(timeoutMs: Int) = waitForIdle(timeoutMs.toLong()) -fun Handler.waitForIdle(timeoutMs: Long) { - val cv = ConditionVariable(false) - post(cv::open) - if (!cv.block(timeoutMs)) { - fail("Handler did not become idle after ${timeoutMs}ms") - } -} - -/** - * Block until the given Serial Executor becomes idle, or until timeoutMs has passed. - */ -fun waitForIdleSerialExecutor(executor: Executor, timeoutMs: Long) { - val cv = ConditionVariable() - executor.execute(cv::open) - if (!cv.block(timeoutMs)) { - fail("Executor did not become idle after ${timeoutMs}ms") - } -} - -/** - * Executes a block of code that returns a value, making its side effects visible on the caller and - * the handler thread. - * - * After this function returns, the side effects of the passed block of code are guaranteed to be - * observed both on the thread running the handler and on the thread running this method. - * To achieve this, this method runs the passed block on the handler and blocks this thread - * until it's executed, so keep in mind this method will block, (including, if the handler isn't - * running, blocking forever). - */ -fun <T> visibleOnHandlerThread(handler: Handler, supplier: ThrowingSupplier<T>): T { - val cv = ConditionVariable() - var rv: Result<T> = Result.failure(RuntimeException("Not run")) - handler.post { - try { - rv = Result.success(supplier.get()) - } catch (exception: Exception) { - Log.e(TAG, "visibleOnHandlerThread caught exception", exception) - rv = Result.failure(exception) - } - cv.open() - } - // After block() returns, the handler thread has seen the change (since it ran it) - // and this thread also has seen the change (since cv.open() happens-before cv.block() - // returns). - cv.block() - return rv.getOrThrow() -} - -/** Overload of visibleOnHandlerThread but executes a block of code that does not return a value. */ -inline fun visibleOnHandlerThread(handler: Handler, r: ThrowingRunnable){ - visibleOnHandlerThread(handler, ThrowingSupplier<Unit> { r.run() }) -} diff --git a/common/testutils/devicetests/com/android/testutils/NatExternalPacketForwarder.kt b/common/testutils/devicetests/com/android/testutils/NatExternalPacketForwarder.kt deleted file mode 100644 index d7961a08..00000000 --- a/common/testutils/devicetests/com/android/testutils/NatExternalPacketForwarder.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import java.io.FileDescriptor -import java.net.InetAddress - -/** - * A class that forwards packets from the external {@link TestNetworkInterface} to the internal - * {@link TestNetworkInterface} with NAT. See {@link NatPacketForwarderBase} for detail. - */ -class NatExternalPacketForwarder( - srcFd: FileDescriptor, - mtu: Int, - dstFd: FileDescriptor, - extAddr: InetAddress, - natMap: PacketBridge.NatMap -) : NatPacketForwarderBase(srcFd, mtu, dstFd, extAddr, natMap) { - - /** - * Rewrite addresses, ports and fix up checksums for packets received on the external - * interface. - * - * Incoming response from external interface which is being forwarded to the internal - * interface with translated address, e.g. 1.2.3.4:80 -> 8.8.8.8:1234 - * will be translated into 8.8.8.8:80 -> 192.168.1.1:5678. - * - * For packets that are not an incoming response, do not forward them to the - * internal interface. - */ - override fun preparePacketForForwarding(buf: ByteArray, len: Int, version: Int, proto: Int) { - val (addrPos, addrLen) = getAddressPositionAndLength(version) - - // TODO: support one external address per ip version. - val extAddrBuf = mExtAddr.address - if (addrLen != extAddrBuf.size) throw IllegalStateException("Packet IP version mismatch") - - // Get internal address by port. - val transportOffset = - if (version == 4) PacketReflector.IPV4_HEADER_LENGTH - else PacketReflector.IPV6_HEADER_LENGTH - val dstPort = getPortAt(buf, transportOffset + DESTINATION_PORT_OFFSET) - val intAddrInfo = synchronized(mNatMap) { mNatMap.fromExternalPort(dstPort) } - // No mapping, skip. This usually happens if the connection is initiated directly on - // the external interface, e.g. DNS64 resolution, network validation, etc. - if (intAddrInfo == null) return - - val intAddrBuf = intAddrInfo.address.address - val intPort = intAddrInfo.port - - // Copy the original destination to into the source address. - for (i in 0 until addrLen) { - buf[addrPos + i] = buf[addrPos + addrLen + i] - } - - // Copy the internal address into the destination address. - for (i in 0 until addrLen) { - buf[addrPos + addrLen + i] = intAddrBuf[i] - } - - // Copy the internal port into the destination port. - setPortAt(intPort, buf, transportOffset + DESTINATION_PORT_OFFSET) - - // Fix IP and Transport layer checksum. - fixPacketChecksum(buf, len, version, proto.toByte()) - } -} diff --git a/common/testutils/devicetests/com/android/testutils/NatInternalPacketForwarder.kt b/common/testutils/devicetests/com/android/testutils/NatInternalPacketForwarder.kt deleted file mode 100644 index fa39d19e..00000000 --- a/common/testutils/devicetests/com/android/testutils/NatInternalPacketForwarder.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import java.io.FileDescriptor -import java.net.InetAddress - -/** - * A class that forwards packets from the internal {@link TestNetworkInterface} to the external - * {@link TestNetworkInterface} with NAT. See {@link NatPacketForwarderBase} for detail. - */ -class NatInternalPacketForwarder( - srcFd: FileDescriptor, - mtu: Int, - dstFd: FileDescriptor, - extAddr: InetAddress, - natMap: PacketBridge.NatMap -) : NatPacketForwarderBase(srcFd, mtu, dstFd, extAddr, natMap) { - - /** - * Rewrite addresses, ports and fix up checksums for packets received on the internal - * interface. - * - * Outgoing packet from the internal interface which is being forwarded to the - * external interface with translated address, e.g. 192.168.1.1:5678 -> 8.8.8.8:80 - * will be translated into 8.8.8.8:1234 -> 1.2.3.4:80. - * - * The external port, e.g. 1234 in the above example, is the port number assigned by - * the forwarder when creating the mapping to identify the source address and port when - * the response is coming from the external interface. See {@link PacketBridge.NatMap} - * for detail. - */ - override fun preparePacketForForwarding(buf: ByteArray, len: Int, version: Int, proto: Int) { - val (addrPos, addrLen) = getAddressPositionAndLength(version) - - // TODO: support one external address per ip version. - val extAddrBuf = mExtAddr.address - if (addrLen != extAddrBuf.size) throw IllegalStateException("Packet IP version mismatch") - - val srcAddr = getInetAddressAt(buf, addrPos, addrLen) - - // Copy the original destination to into the source address. - for (i in 0 until addrLen) { - buf[addrPos + i] = buf[addrPos + addrLen + i] - } - - // Copy the external address into the destination address. - for (i in 0 until addrLen) { - buf[addrPos + addrLen + i] = extAddrBuf[i] - } - - // Add an entry to NAT mapping table. - val transportOffset = - if (version == 4) PacketReflector.IPV4_HEADER_LENGTH - else PacketReflector.IPV6_HEADER_LENGTH - val srcPort = getPortAt(buf, transportOffset) - val extPort = synchronized(mNatMap) { mNatMap.toExternalPort(srcAddr, srcPort, proto) } - // Copy the external port to into the source port. - setPortAt(extPort, buf, transportOffset) - - // Fix IP and Transport layer checksum. - fixPacketChecksum(buf, len, version, proto.toByte()) - } -} diff --git a/common/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java b/common/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java deleted file mode 100644 index 0a2b5d41..00000000 --- a/common/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils; - -import static com.android.testutils.PacketReflector.IPPROTO_TCP; -import static com.android.testutils.PacketReflector.IPPROTO_UDP; -import static com.android.testutils.PacketReflector.IPV4_HEADER_LENGTH; -import static com.android.testutils.PacketReflector.IPV6_HEADER_LENGTH; -import static com.android.testutils.PacketReflector.IPV6_PROTO_OFFSET; -import static com.android.testutils.PacketReflector.TCP_HEADER_LENGTH; -import static com.android.testutils.PacketReflector.UDP_HEADER_LENGTH; - -import android.annotation.NonNull; -import android.net.TestNetworkInterface; -import android.system.ErrnoException; -import android.system.Os; -import android.util.Log; - -import androidx.annotation.GuardedBy; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.net.InetAddress; -import java.util.Objects; - -/** - * A class that forwards packets from a {@link TestNetworkInterface} to another - * {@link TestNetworkInterface} with NAT. - * - * For testing purposes, a {@link TestNetworkInterface} provides a {@link FileDescriptor} - * which allows content injection on the test network. However, this could be hard to use - * because the callers need to compose IP packets in order to inject content to the - * test network. - * - * In order to remove the need of composing the IP packets, this class forwards IP packets to - * the {@link FileDescriptor} of another {@link TestNetworkInterface} instance. Thus, - * the TCP/IP headers could be parsed/composed automatically by the protocol stack of this - * additional {@link TestNetworkInterface}, while the payload is supplied by the - * servers run on the interface. - * - * To make it work, an internal interface and an external interface are defined, where - * the client might send packets from the internal interface which are originated from - * multiple addresses to a server that listens on the external address. - * - * When forwarding the outgoing packet on the internal interface, a simple NAT mechanism - * is implemented during forwarding, which will swap the source and destination, - * but replacing the source address with the external address, - * e.g. 192.168.1.1:1234 -> 8.8.8.8:80 will be translated into 8.8.8.8:1234 -> 1.2.3.4:80. - * - * For the above example, a client who sends http request will have a hallucination that - * it is talking to a remote server at 8.8.8.8. Also, the server listens on 1.2.3.4 will - * have a different hallucination that the request is sent from a remote client at 8.8.8.8, - * to a local address 1.2.3.4. - * - * And a NAT mapping is created at the time when the outgoing packet is forwarded. - * With a different internal source port, the instance learned that when a response with the - * destination port 1234, it should forward the packet to the internal address 192.168.1.1. - * - * For the incoming packet received from external interface, for example a http response sent - * from the http server, the same mechanism is applied but in a different direction, - * where the source and destination will be swapped, and the source address will be replaced - * with the internal address, which is obtained from the NAT mapping described above. - */ -public abstract class NatPacketForwarderBase extends Thread { - private static final String TAG = "NatPacketForwarder"; - static final int DESTINATION_PORT_OFFSET = 2; - - // The source fd to read packets from. - @NonNull - final FileDescriptor mSrcFd; - // The buffer to temporarily hold the entire packet after receiving. - @NonNull - final byte[] mBuf; - // The destination fd to write packets to. - @NonNull - final FileDescriptor mDstFd; - // The NAT mapping table shared between two NatPacketForwarder instances to map from - // the source port to the associated internal address. The map can be read/write from two - // different threads on any given time whenever receiving packets on the - // {@link TestNetworkInterface}. Thus, synchronize on the object when reading/writing is needed. - @GuardedBy("mNatMap") - @NonNull - final PacketBridge.NatMap mNatMap; - // The address of the external interface. See {@link NatPacketForwarder}. - @NonNull - final InetAddress mExtAddr; - - /** - * Construct a {@link NatPacketForwarderBase}. - * - * This class reads packets from {@code srcFd} of a {@link TestNetworkInterface}, and - * forwards them to the {@code dstFd} of another {@link TestNetworkInterface} with - * NAT applied. See {@link NatPacketForwarderBase}. - * - * To apply NAT, the address of the external interface needs to be supplied through - * {@code extAddr} to identify the external interface. And a shared NAT mapping table, - * {@code natMap} is needed to be shared between these two instances. - * - * Note that this class is not useful if the instance is not managed by a - * {@link PacketBridge} to set up a two-way communication. - * - * @param srcFd {@link FileDescriptor} to read packets from. - * @param mtu MTU of the test network. - * @param dstFd {@link FileDescriptor} to write packets to. - * @param extAddr the external address, which is the address of the external interface. - * See {@link NatPacketForwarderBase}. - * @param natMap the NAT mapping table shared between two {@link NatPacketForwarderBase} - * instance. - */ - public NatPacketForwarderBase(@NonNull FileDescriptor srcFd, int mtu, - @NonNull FileDescriptor dstFd, @NonNull InetAddress extAddr, - @NonNull PacketBridge.NatMap natMap) { - super(TAG); - mSrcFd = Objects.requireNonNull(srcFd); - mBuf = new byte[mtu]; - mDstFd = Objects.requireNonNull(dstFd); - mExtAddr = Objects.requireNonNull(extAddr); - mNatMap = Objects.requireNonNull(natMap); - } - - /** - * A method to prepare forwarding packets between two instances of {@link TestNetworkInterface}, - * which includes re-write addresses, ports and fix up checksums. - * Subclasses should override this method to implement a simple NAT. - */ - abstract void preparePacketForForwarding(@NonNull byte[] buf, int len, int version, int proto); - - private void forwardPacket(@NonNull byte[] buf, int len) { - try { - Os.write(mDstFd, buf, 0, len); - } catch (ErrnoException | IOException e) { - Log.e(TAG, "Error writing packet: " + e.getMessage()); - } - } - - // Reads one packet from mSrcFd, and writes the packet to the mDstFd for supported protocols. - private void processPacket() { - final int len = PacketReflectorUtil.readPacket(mSrcFd, mBuf); - if (len < 1) { - // Usually happens when socket read is being interrupted, e.g. stopping PacketForwarder. - return; - } - - final int version = mBuf[0] >>> 4; - final int protoPos, ipHdrLen; - switch (version) { - case 4: - ipHdrLen = IPV4_HEADER_LENGTH; - protoPos = PacketReflector.IPV4_PROTO_OFFSET; - break; - case 6: - ipHdrLen = IPV6_HEADER_LENGTH; - protoPos = IPV6_PROTO_OFFSET; - break; - default: - throw new IllegalStateException("Unexpected version: " + version); - } - if (len < ipHdrLen) { - throw new IllegalStateException("Unexpected buffer length: " + len); - } - - final byte proto = mBuf[protoPos]; - final int transportHdrLen; - switch (proto) { - case IPPROTO_TCP: - transportHdrLen = TCP_HEADER_LENGTH; - break; - case IPPROTO_UDP: - transportHdrLen = UDP_HEADER_LENGTH; - break; - // TODO: Support ICMP. - default: - return; // Unknown protocol, ignored. - } - - if (len < ipHdrLen + transportHdrLen) { - throw new IllegalStateException("Unexpected buffer length: " + len); - } - // Re-write addresses, ports and fix up checksums. - preparePacketForForwarding(mBuf, len, version, proto); - // Send the packet to the destination fd. - forwardPacket(mBuf, len); - } - - @Override - public void run() { - Log.i(TAG, "starting fd=" + mSrcFd + " valid=" + mSrcFd.valid()); - while (!interrupted() && mSrcFd.valid()) { - processPacket(); - } - Log.i(TAG, "exiting fd=" + mSrcFd + " valid=" + mSrcFd.valid()); - } -} diff --git a/common/testutils/devicetests/com/android/testutils/NetlinkTestUtils.kt b/common/testutils/devicetests/com/android/testutils/NetlinkTestUtils.kt deleted file mode 100644 index 3f5460bf..00000000 --- a/common/testutils/devicetests/com/android/testutils/NetlinkTestUtils.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:JvmName("NetlinkTestUtils") - -package com.android.testutils - -import com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH -import com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH -import libcore.util.HexEncoding -import libcore.util.HexEncoding.encodeToString -import java.net.Inet6Address -import java.net.InetAddress - -private const val VRRP_MAC_ADDR = "00005e000164" - -/** - * Make a RTM_NEWNEIGH netlink message. - */ -@JvmOverloads -fun makeNewNeighMessage( - neighAddr: InetAddress, - nudState: Short, - linkLayerAddr: String = VRRP_MAC_ADDR -) = makeNeighborMessage( - neighAddr = neighAddr, - type = RTM_NEWNEIGH, - nudState = nudState, - linkLayerAddr = linkLayerAddr -) - -/** - * Make a RTM_DELNEIGH netlink message. - */ -fun makeDelNeighMessage( - neighAddr: InetAddress, - nudState: Short -) = makeNeighborMessage( - neighAddr = neighAddr, - type = RTM_DELNEIGH, - nudState = nudState -) - -private fun makeNeighborMessage( - neighAddr: InetAddress, - type: Short, - nudState: Short, - linkLayerAddr: String = VRRP_MAC_ADDR -) = HexEncoding.decode( - /* ktlint-disable indent */ - // -- struct nlmsghdr -- - // length = 88 or 76: - (if (neighAddr is Inet6Address) "58000000" else "4c000000") + - type.toLEHex() + // type - "0000" + // flags - "00000000" + // seqno - "00000000" + // pid (0 == kernel) - // struct ndmsg - // family (AF_INET6 or AF_INET) - (if (neighAddr is Inet6Address) "0a" else "02") + - "00" + // pad1 - "0000" + // pad2 - "15000000" + // interface index (21 == wlan0, on test device) - nudState.toLEHex() + // NUD state - "00" + // flags - "01" + // type - // -- struct nlattr: NDA_DST -- - // length = 20 or 8: - (if (neighAddr is Inet6Address) "1400" else "0800") + - "0100" + // type (1 == NDA_DST, for neighbor messages) - // IP address: - encodeToString(neighAddr.address) + - // -- struct nlattr: NDA_LLADDR -- - "0a00" + // length = 10 - "0200" + // type (2 == NDA_LLADDR, for neighbor messages) - linkLayerAddr + // MAC Address(default == 00:00:5e:00:01:64) - "0000" + // padding, for 4 byte alignment - // -- struct nlattr: NDA_PROBES -- - "0800" + // length = 8 - "0400" + // type (4 == NDA_PROBES, for neighbor messages) - "01000000" + // number of probes - // -- struct nlattr: NDA_CACHEINFO -- - "1400" + // length = 20 - "0300" + // type (3 == NDA_CACHEINFO, for neighbor messages) - "05190000" + // ndm_used, as "clock ticks ago" - "05190000" + // ndm_confirmed, as "clock ticks ago" - "190d0000" + // ndm_updated, as "clock ticks ago" - "00000000", // ndm_refcnt - false /* allowSingleChar */) - /* ktlint-enable indent */ - -/** - * Convert a [Short] to a little-endian hex string. - */ -private fun Short.toLEHex() = String.format("%04x", java.lang.Short.reverseBytes(this)) diff --git a/common/testutils/devicetests/com/android/testutils/NetworkStatsProviderCbStubCompat.java b/common/testutils/devicetests/com/android/testutils/NetworkStatsProviderCbStubCompat.java deleted file mode 100644 index 642da7ac..00000000 --- a/common/testutils/devicetests/com/android/testutils/NetworkStatsProviderCbStubCompat.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils; - -import android.net.NetworkStats; -import android.net.netstats.provider.INetworkStatsProviderCallback; -import android.os.RemoteException; - -/** - * A shim class that allows {@link TestableNetworkStatsProviderCbBinder} to be built against - * different SDK versions. - */ -public class NetworkStatsProviderCbStubCompat extends INetworkStatsProviderCallback.Stub { - @Override - public void notifyStatsUpdated(int token, NetworkStats ifaceStats, NetworkStats uidStats) - throws RemoteException {} - - @Override - public void notifyAlertReached() throws RemoteException {} - - /** Added in T. */ - public void notifyLimitReached() throws RemoteException {} - - /** Added in T. */ - public void notifyWarningReached() throws RemoteException {} - - /** Added in S, removed in T. */ - public void notifyWarningOrLimitReached() throws RemoteException {} - - @Override - public void unregister() throws RemoteException {} -} diff --git a/common/testutils/devicetests/com/android/testutils/NetworkStatsProviderStubCompat.java b/common/testutils/devicetests/com/android/testutils/NetworkStatsProviderStubCompat.java deleted file mode 100644 index a77aa025..00000000 --- a/common/testutils/devicetests/com/android/testutils/NetworkStatsProviderStubCompat.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils; - -import android.net.netstats.provider.INetworkStatsProvider; - -/** - * A shim class that allows {@link TestableNetworkStatsProviderBinder} to be built against - * different SDK versions. - */ -public class NetworkStatsProviderStubCompat extends INetworkStatsProvider.Stub { - @Override - public void onRequestStatsUpdate(int token) {} - - // Removed and won't be called in S+. - public void onSetLimit(String iface, long quotaBytes) {} - - @Override - public void onSetAlert(long bytes) {} - - // Added in S. - public void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes) {} -} diff --git a/common/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt b/common/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt deleted file mode 100644 index 8324b25e..00000000 --- a/common/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.net.NetworkStats -import kotlin.test.assertTrue - -@JvmOverloads -fun orderInsensitiveEquals( - leftStats: NetworkStats, - rightStats: NetworkStats, - compareTime: Boolean = false -): Boolean { - if (leftStats == rightStats) return true - if (compareTime && leftStats.getElapsedRealtime() != rightStats.getElapsedRealtime()) { - return false - } - - // While operations such as add/subtract will preserve empty entries. This will make - // the result be hard to verify during test. Remove them before comparing since they - // are not really affect correctness. - // TODO (b/152827872): Remove empty entries after addition/subtraction. - val leftTrimmedEmpty = leftStats.removeEmptyEntries() - val rightTrimmedEmpty = rightStats.removeEmptyEntries() - - if (leftTrimmedEmpty.size() != rightTrimmedEmpty.size()) return false - val left = NetworkStats.Entry() - val right = NetworkStats.Entry() - // Order insensitive compare. - for (i in 0 until leftTrimmedEmpty.size()) { - leftTrimmedEmpty.getValues(i, left) - val j: Int = rightTrimmedEmpty.findIndexHinted(left.iface, left.uid, left.set, left.tag, - left.metered, left.roaming, left.defaultNetwork, i) - if (j == -1) return false - rightTrimmedEmpty.getValues(j, right) - if (left != right) return false - } - return true -} - -/** - * Assert that two {@link NetworkStats} are equals, assuming the order of the records are not - * necessarily the same. - * - * @note {@code elapsedRealtime} is not compared by default, given that in test cases that is not - * usually used. - */ -@JvmOverloads -fun assertNetworkStatsEquals( - expected: NetworkStats, - actual: NetworkStats, - compareTime: Boolean = false -) { - assertTrue(orderInsensitiveEquals(expected, actual, compareTime), - "expected: " + expected + " but was: " + actual) -} - -/** - * Assert that after being parceled then unparceled, {@link NetworkStats} is equal to the original - * object. - */ -fun assertParcelingIsLossless(stats: NetworkStats) { - assertParcelingIsLossless(stats, { a, b -> orderInsensitiveEquals(a, b) }) -} diff --git a/common/testutils/devicetests/com/android/testutils/NonNullTestUtils.java b/common/testutils/devicetests/com/android/testutils/NonNullTestUtils.java deleted file mode 100644 index 463c4706..00000000 --- a/common/testutils/devicetests/com/android/testutils/NonNullTestUtils.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils; - -import android.annotation.NonNull; - -/** - * Utilities to help Kotlin test to verify java @NonNull code - */ -public class NonNullTestUtils { - /** - * This method allows Kotlin to pass nullable to @NonNull java code for testing. - * For Foo(@NonNull arg) java method, Kotlin can pass nullable variable by - * Foo(NonNullTestUtils.nullUnsafe(nullableVar)). - */ - @NonNull public static <T> T nullUnsafe(T v) { - return v; - } -} diff --git a/common/testutils/devicetests/com/android/testutils/PacketBridge.kt b/common/testutils/devicetests/com/android/testutils/PacketBridge.kt deleted file mode 100644 index d50f78a1..00000000 --- a/common/testutils/devicetests/com/android/testutils/PacketBridge.kt +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.content.Context -import android.net.ConnectivityManager -import android.net.LinkAddress -import android.net.LinkProperties -import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkRequest -import android.net.TestNetworkInterface -import android.net.TestNetworkManager -import android.net.TestNetworkSpecifier -import android.os.Binder -import com.android.testutils.RecorderCallback.CallbackEntry.Available -import java.net.InetAddress -import libcore.io.IoUtils - -private const val MIN_PORT_NUMBER = 1025 -private const val MAX_PORT_NUMBER = 65535 - -/** - * A class that set up two {@link TestNetworkInterface} with NAT, and forward packets between them. - * - * See {@link NatPacketForwarder} for more detailed information. - */ -class PacketBridge( - context: Context, - internalAddr: LinkAddress, - externalAddr: LinkAddress, - dnsAddr: InetAddress -) { - private val natMap = NatMap() - private val binder = Binder() - - private val cm = context.getSystemService(ConnectivityManager::class.java)!! - private val tnm = context.getSystemService(TestNetworkManager::class.java)!! - - // Create test networks. - private val internalIface = tnm.createTunInterface(listOf(internalAddr)) - private val externalIface = tnm.createTunInterface(listOf(externalAddr)) - - // Register test networks to ConnectivityService. - private val internalNetworkCallback: TestableNetworkCallback - private val externalNetworkCallback: TestableNetworkCallback - val internalNetwork: Network - val externalNetwork: Network - init { - val (inCb, inNet) = createTestNetwork(internalIface, internalAddr, dnsAddr) - val (exCb, exNet) = createTestNetwork(externalIface, externalAddr, dnsAddr) - internalNetworkCallback = inCb - externalNetworkCallback = exCb - internalNetwork = inNet - externalNetwork = exNet - } - - // Setup the packet bridge. - private val internalFd = internalIface.fileDescriptor.fileDescriptor - private val externalFd = externalIface.fileDescriptor.fileDescriptor - - private val pr1 = NatInternalPacketForwarder( - internalFd, - 1500, - externalFd, - externalAddr.address, - natMap - ) - private val pr2 = NatExternalPacketForwarder( - externalFd, - 1500, - internalFd, - externalAddr.address, - natMap - ) - - fun start() { - IoUtils.setBlocking(internalFd, true /* blocking */) - IoUtils.setBlocking(externalFd, true /* blocking */) - pr1.start() - pr2.start() - } - - fun stop() { - pr1.interrupt() - pr2.interrupt() - cm.unregisterNetworkCallback(internalNetworkCallback) - cm.unregisterNetworkCallback(externalNetworkCallback) - } - - /** - * Creates a test network with given test TUN interface and addresses. - */ - private fun createTestNetwork( - testIface: TestNetworkInterface, - addr: LinkAddress, - dnsAddr: InetAddress - ): Pair<TestableNetworkCallback, Network> { - // Make a network request to hold the test network - val nr = NetworkRequest.Builder() - .clearCapabilities() - .addTransportType(NetworkCapabilities.TRANSPORT_TEST) - .setNetworkSpecifier(TestNetworkSpecifier(testIface.interfaceName)) - .build() - val testCb = TestableNetworkCallback() - cm.requestNetwork(nr, testCb) - - val lp = LinkProperties().apply { - addLinkAddress(addr) - interfaceName = testIface.interfaceName - addDnsServer(dnsAddr) - } - tnm.setupTestNetwork(lp, true /* isMetered */, binder) - - // Wait for available before return. - val network = testCb.expect<Available>().network - return testCb to network - } - - /** - * A helper class to maintain the mappings between internal addresses/ports and external - * ports. - * - * This class assigns an unused external port number if the mapping between - * srcaddress:srcport:protocol and the external port does not exist yet. - * - * Note that this class is not thread-safe. The instance of the class needs to be - * synchronized in the callers when being used in multiple threads. - */ - class NatMap { - data class AddressInfo(val address: InetAddress, val port: Int, val protocol: Int) - - private val mToExternalPort = HashMap<AddressInfo, Int>() - private val mFromExternalPort = HashMap<Int, AddressInfo>() - - // Skip well-known port 0~1024. - private var nextExternalPort = MIN_PORT_NUMBER - - fun toExternalPort(addr: InetAddress, port: Int, protocol: Int): Int { - val info = AddressInfo(addr, port, protocol) - val extPort: Int - if (!mToExternalPort.containsKey(info)) { - extPort = nextExternalPort++ - if (nextExternalPort > MAX_PORT_NUMBER) { - throw IllegalStateException("Available ports are exhausted") - } - mToExternalPort[info] = extPort - mFromExternalPort[extPort] = info - } else { - extPort = mToExternalPort[info]!! - } - return extPort - } - - fun fromExternalPort(port: Int): AddressInfo? { - return mFromExternalPort[port] - } - } -} diff --git a/common/testutils/devicetests/com/android/testutils/PacketReflector.java b/common/testutils/devicetests/com/android/testutils/PacketReflector.java deleted file mode 100644 index 69392d44..00000000 --- a/common/testutils/devicetests/com/android/testutils/PacketReflector.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils; - -import static android.system.OsConstants.ICMP6_ECHO_REPLY; -import static android.system.OsConstants.ICMP6_ECHO_REQUEST; - -import android.annotation.NonNull; -import android.net.TestNetworkInterface; -import android.system.ErrnoException; -import android.system.Os; -import android.util.Log; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.util.Objects; - -/** - * A class that echoes packets received on a {@link TestNetworkInterface} back to itself. - * - * For testing purposes, sometimes a mocked environment to simulate a simple echo from the - * server side is needed. This is particularly useful if the test, e.g. VpnTest, is - * heavily relying on the outside world. - * - * This class reads packets from the {@link FileDescriptor} of a {@link TestNetworkInterface}, and: - * 1. For TCP and UDP packets, simply swaps the source address and the destination - * address, then send it back to the {@link FileDescriptor}. - * 2. For ICMP ping packets, composes a ping reply and sends it back to the sender. - * 3. Ignore all other packets. - */ -public class PacketReflector extends Thread { - - static final int IPV4_HEADER_LENGTH = 20; - static final int IPV6_HEADER_LENGTH = 40; - - static final int IPV4_ADDR_OFFSET = 12; - static final int IPV6_ADDR_OFFSET = 8; - static final int IPV4_ADDR_LENGTH = 4; - static final int IPV6_ADDR_LENGTH = 16; - - static final int IPV4_PROTO_OFFSET = 9; - static final int IPV6_PROTO_OFFSET = 6; - - static final byte IPPROTO_ICMP = 1; - static final byte IPPROTO_TCP = 6; - static final byte IPPROTO_UDP = 17; - private static final byte IPPROTO_ICMPV6 = 58; - - private static final int ICMP_HEADER_LENGTH = 8; - static final int TCP_HEADER_LENGTH = 20; - static final int UDP_HEADER_LENGTH = 8; - - private static final byte ICMP_ECHO = 8; - private static final byte ICMP_ECHOREPLY = 0; - - private static String TAG = "PacketReflector"; - - @NonNull - private final FileDescriptor mFd; - @NonNull - private final byte[] mBuf; - - /** - * Construct a {@link PacketReflector} from the given {@code fd} of - * a {@link TestNetworkInterface}. - * - * @param fd {@link FileDescriptor} to read/write packets. - * @param mtu MTU of the test network. - */ - public PacketReflector(@NonNull FileDescriptor fd, int mtu) { - super("PacketReflector"); - mFd = Objects.requireNonNull(fd); - mBuf = new byte[mtu]; - } - - private static void swapBytes(@NonNull byte[] buf, int pos1, int pos2, int len) { - for (int i = 0; i < len; i++) { - byte b = buf[pos1 + i]; - buf[pos1 + i] = buf[pos2 + i]; - buf[pos2 + i] = b; - } - } - - private static void swapAddresses(@NonNull byte[] buf, int version) { - int addrPos, addrLen; - switch (version) { - case 4: - addrPos = IPV4_ADDR_OFFSET; - addrLen = IPV4_ADDR_LENGTH; - break; - case 6: - addrPos = IPV6_ADDR_OFFSET; - addrLen = IPV6_ADDR_LENGTH; - break; - default: - throw new IllegalArgumentException(); - } - swapBytes(buf, addrPos, addrPos + addrLen, addrLen); - } - - // Reflect TCP packets: swap the source and destination addresses, but don't change the ports. - // This is used by the test to "connect to itself" through the VPN. - private void processTcpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) { - if (len < hdrLen + TCP_HEADER_LENGTH) { - return; - } - - // Swap src and dst IP addresses. - swapAddresses(buf, version); - - // Send the packet back. - writePacket(buf, len); - } - - // Echo UDP packets: swap source and destination addresses, and source and destination ports. - // This is used by the test to check that the bytes it sends are echoed back. - private void processUdpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) { - if (len < hdrLen + UDP_HEADER_LENGTH) { - return; - } - - // Swap src and dst IP addresses. - swapAddresses(buf, version); - - // Swap dst and src ports. - int portOffset = hdrLen; - swapBytes(buf, portOffset, portOffset + 2, 2); - - // Send the packet back. - writePacket(buf, len); - } - - private void processIcmpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) { - if (len < hdrLen + ICMP_HEADER_LENGTH) { - return; - } - - byte type = buf[hdrLen]; - if (!(version == 4 && type == ICMP_ECHO) && - !(version == 6 && type == (byte) ICMP6_ECHO_REQUEST)) { - return; - } - - // Save the ping packet we received. - byte[] request = buf.clone(); - - // Swap src and dst IP addresses, and send the packet back. - // This effectively pings the device to see if it replies. - swapAddresses(buf, version); - writePacket(buf, len); - - // The device should have replied, and buf should now contain a ping response. - int received = PacketReflectorUtil.readPacket(mFd, buf); - if (received != len) { - Log.i(TAG, "Reflecting ping did not result in ping response: " + - "read=" + received + " expected=" + len); - return; - } - - byte replyType = buf[hdrLen]; - if ((type == ICMP_ECHO && replyType != ICMP_ECHOREPLY) - || (type == (byte) ICMP6_ECHO_REQUEST && replyType != (byte) ICMP6_ECHO_REPLY)) { - Log.i(TAG, "Received unexpected ICMP reply: original " + type - + ", reply " + replyType); - return; - } - - // Compare the response we got with the original packet. - // The only thing that should have changed are addresses, type and checksum. - // Overwrite them with the received bytes and see if the packet is otherwise identical. - request[hdrLen] = buf[hdrLen]; // Type - request[hdrLen + 2] = buf[hdrLen + 2]; // Checksum byte 1. - request[hdrLen + 3] = buf[hdrLen + 3]; // Checksum byte 2. - - // Since Linux kernel 4.2, net.ipv6.auto_flowlabels is set by default, and therefore - // the request and reply may have different IPv6 flow label: ignore that as well. - if (version == 6) { - request[1] = (byte) (request[1] & 0xf0 | buf[1] & 0x0f); - request[2] = buf[2]; - request[3] = buf[3]; - } - - for (int i = 0; i < len; i++) { - if (buf[i] != request[i]) { - Log.i(TAG, "Received non-matching packet when expecting ping response."); - return; - } - } - - // Now swap the addresses again and reflect the packet. This sends a ping reply. - swapAddresses(buf, version); - writePacket(buf, len); - } - - private void writePacket(@NonNull byte[] buf, int len) { - try { - Os.write(mFd, buf, 0, len); - } catch (ErrnoException | IOException e) { - Log.e(TAG, "Error writing packet: " + e.getMessage()); - } - } - - // Reads one packet from our mFd, and possibly writes the packet back. - private void processPacket() { - int len = PacketReflectorUtil.readPacket(mFd, mBuf); - if (len < 1) { - // Usually happens when socket read is being interrupted, e.g. stopping PacketReflector. - return; - } - - int version = mBuf[0] >> 4; - int protoPos, hdrLen; - if (version == 4) { - hdrLen = IPV4_HEADER_LENGTH; - protoPos = IPV4_PROTO_OFFSET; - } else if (version == 6) { - hdrLen = IPV6_HEADER_LENGTH; - protoPos = IPV6_PROTO_OFFSET; - } else { - throw new IllegalStateException("Unexpected version: " + version); - } - - if (len < hdrLen) { - throw new IllegalStateException("Unexpected buffer length: " + len); - } - - byte proto = mBuf[protoPos]; - switch (proto) { - case IPPROTO_ICMP: - // fall through - case IPPROTO_ICMPV6: - processIcmpPacket(mBuf, version, len, hdrLen); - break; - case IPPROTO_TCP: - processTcpPacket(mBuf, version, len, hdrLen); - break; - case IPPROTO_UDP: - processUdpPacket(mBuf, version, len, hdrLen); - break; - } - } - - public void run() { - Log.i(TAG, "starting fd=" + mFd + " valid=" + mFd.valid()); - while (!interrupted() && mFd.valid()) { - processPacket(); - } - Log.i(TAG, "exiting fd=" + mFd + " valid=" + mFd.valid()); - } -} diff --git a/common/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt b/common/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt deleted file mode 100644 index 498b1a36..00000000 --- a/common/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:JvmName("PacketReflectorUtil") - -package com.android.testutils - -import android.system.ErrnoException -import android.system.Os -import android.system.OsConstants -import com.android.net.module.util.IpUtils -import com.android.testutils.PacketReflector.IPV4_HEADER_LENGTH -import com.android.testutils.PacketReflector.IPV6_HEADER_LENGTH -import java.io.FileDescriptor -import java.io.InterruptedIOException -import java.net.InetAddress -import java.nio.ByteBuffer - -fun readPacket(fd: FileDescriptor, buf: ByteArray): Int { - return try { - Os.read(fd, buf, 0, buf.size) - } catch (e: ErrnoException) { - // Ignore normal use cases such as the EAGAIN error indicates that the read operation - // cannot be completed immediately, or the EINTR error indicates that the read - // operation was interrupted by a signal. - if (e.errno == OsConstants.EAGAIN || e.errno == OsConstants.EINTR) { - -1 - } else { - throw e - } - } catch (e: InterruptedIOException) { - -1 - } -} - -fun getInetAddressAt(buf: ByteArray, pos: Int, len: Int): InetAddress = - InetAddress.getByAddress(buf.copyOfRange(pos, pos + len)) - -/** - * Reads a 16-bit unsigned int at pos in big endian, with no alignment requirements. - */ -fun getPortAt(buf: ByteArray, pos: Int): Int { - return (buf[pos].toInt() and 0xff shl 8) + (buf[pos + 1].toInt() and 0xff) -} - -fun setPortAt(port: Int, buf: ByteArray, pos: Int) { - buf[pos] = (port ushr 8).toByte() - buf[pos + 1] = (port and 0xff).toByte() -} - -fun getAddressPositionAndLength(version: Int) = when (version) { - 4 -> PacketReflector.IPV4_ADDR_OFFSET to PacketReflector.IPV4_ADDR_LENGTH - 6 -> PacketReflector.IPV6_ADDR_OFFSET to PacketReflector.IPV6_ADDR_LENGTH - else -> throw IllegalArgumentException("Unknown IP version $version") -} - -private const val IPV4_CHKSUM_OFFSET = 10 -private const val UDP_CHECKSUM_OFFSET = 6 -private const val TCP_CHECKSUM_OFFSET = 16 - -fun fixPacketChecksum(buf: ByteArray, len: Int, version: Int, protocol: Byte) { - // Fill Ip checksum for IPv4. IPv6 header doesn't have a checksum field. - if (version == 4) { - val checksum = IpUtils.ipChecksum(ByteBuffer.wrap(buf), 0) - // Place checksum in Big-endian order. - buf[IPV4_CHKSUM_OFFSET] = (checksum.toInt() ushr 8).toByte() - buf[IPV4_CHKSUM_OFFSET + 1] = (checksum.toInt() and 0xff).toByte() - } - - // Fill transport layer checksum. - val transportOffset = if (version == 4) IPV4_HEADER_LENGTH else IPV6_HEADER_LENGTH - when (protocol) { - PacketReflector.IPPROTO_UDP -> { - val checksumPos = transportOffset + UDP_CHECKSUM_OFFSET - // Clear before calculate. - buf[checksumPos + 1] = 0x00 - buf[checksumPos] = buf[checksumPos + 1] - val checksum = IpUtils.udpChecksum( - ByteBuffer.wrap(buf), 0, - transportOffset - ) - buf[checksumPos] = (checksum.toInt() ushr 8).toByte() - buf[checksumPos + 1] = (checksum.toInt() and 0xff).toByte() - } - PacketReflector.IPPROTO_TCP -> { - val checksumPos = transportOffset + TCP_CHECKSUM_OFFSET - // Clear before calculate. - buf[checksumPos + 1] = 0x00 - buf[checksumPos] = buf[checksumPos + 1] - val transportLen: Int = len - transportOffset - val checksum = IpUtils.tcpChecksum( - ByteBuffer.wrap(buf), 0, transportOffset, - transportLen - ) - buf[checksumPos] = (checksum.toInt() ushr 8).toByte() - buf[checksumPos + 1] = (checksum.toInt() and 0xff).toByte() - } - // TODO: Support ICMP. - else -> throw IllegalArgumentException("Unsupported protocol: $protocol") - } -} diff --git a/common/testutils/devicetests/com/android/testutils/PacketResponder.kt b/common/testutils/devicetests/com/android/testutils/PacketResponder.kt deleted file mode 100644 index 964c6c60..00000000 --- a/common/testutils/devicetests/com/android/testutils/PacketResponder.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import java.util.function.Predicate - -private const val POLL_FREQUENCY_MS = 1000L - -/** - * A class that can be used to reply to packets from a [TapPacketReader]. - * - * A reply thread will be created to reply to incoming packets asynchronously. - * The receiver creates a new read head on the [TapPacketReader], to read packets, so it does not - * affect packets obtained through [TapPacketReader.popPacket]. - * - * @param reader a [TapPacketReader] to obtain incoming packets and reply to them. - * @param packetFilter A filter to apply to incoming packets. - * @param name Name to use for the internal responder thread. - */ -abstract class PacketResponder( - private val reader: TapPacketReader, - private val packetFilter: Predicate<ByteArray>, - name: String -) { - private val replyThread = ReplyThread(name) - - protected abstract fun replyToPacket(packet: ByteArray, reader: TapPacketReader) - - /** - * Start the [PacketResponder]. - */ - fun start() { - replyThread.start() - } - - /** - * Stop the [PacketResponder]. - * - * The responder cannot be used anymore after being stopped. - */ - fun stop() { - replyThread.interrupt() - replyThread.join() - } - - private inner class ReplyThread(name: String) : Thread(name) { - override fun run() { - try { - // Create a new ReadHead so other packets polled on the reader are not affected - val recvPackets = reader.receivedPackets.newReadHead() - while (!isInterrupted) { - recvPackets.poll(POLL_FREQUENCY_MS, packetFilter::test)?.let { - replyToPacket(it, reader) - } - } - } catch (e: InterruptedException) { - // Exit gracefully - } - } - } -} diff --git a/common/testutils/devicetests/com/android/testutils/ParcelUtils.kt b/common/testutils/devicetests/com/android/testutils/ParcelUtils.kt deleted file mode 100644 index 14ed8e9e..00000000 --- a/common/testutils/devicetests/com/android/testutils/ParcelUtils.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:JvmName("ParcelUtils") - -package com.android.testutils - -import android.os.Parcel -import android.os.Parcelable -import kotlin.test.assertTrue -import kotlin.test.fail - -/** - * Return a new instance of `T` after being parceled then unparceled. - */ -fun <T : Parcelable> parcelingRoundTrip(source: T): T { - val creator: Parcelable.Creator<T> - try { - creator = source.javaClass.getField("CREATOR").get(null) as Parcelable.Creator<T> - } catch (e: IllegalAccessException) { - fail("Missing CREATOR field: " + e.message) - } catch (e: NoSuchFieldException) { - fail("Missing CREATOR field: " + e.message) - } - - var p = Parcel.obtain() - source.writeToParcel(p, /* flags */ 0) - p.setDataPosition(0) - val marshalled = p.marshall() - p = Parcel.obtain() - p.unmarshall(marshalled, 0, marshalled.size) - p.setDataPosition(0) - return creator.createFromParcel(p) -} - -/** - * Assert that after being parceled then unparceled, `source` is equal to the original - * object. If a customized equals function is provided, uses the provided one. - */ -@JvmOverloads -fun <T : Parcelable> assertParcelingIsLossless( - source: T, - equals: (T, T) -> Boolean = { a, b -> a == b } -) { - val actual = parcelingRoundTrip(source) - assertTrue(equals(source, actual), "Expected $source, but was $actual") -} - -@JvmOverloads -fun <T : Parcelable> assertParcelSane( - obj: T, - fieldCount: Int, - equals: (T, T) -> Boolean = { a, b -> a == b } -) { - assertFieldCountEquals(fieldCount, obj::class.java) - assertParcelingIsLossless(obj, equals) -} diff --git a/common/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java b/common/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java deleted file mode 100644 index 51d57bc1..00000000 --- a/common/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils; - -import static android.system.OsConstants.IPPROTO_ICMPV6; - -import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6; -import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA; -import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA; -import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION; -import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION; -import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST; -import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE; -import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER; -import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED; -import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS; -import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK; - -import android.net.InetAddresses; -import android.net.IpPrefix; -import android.net.MacAddress; -import android.util.ArrayMap; -import android.util.Log; -import android.util.Pair; - -import com.android.net.module.util.Ipv6Utils; -import com.android.net.module.util.Struct; -import com.android.net.module.util.structs.EthernetHeader; -import com.android.net.module.util.structs.Icmpv6Header; -import com.android.net.module.util.structs.Ipv6Header; -import com.android.net.module.util.structs.LlaOption; -import com.android.net.module.util.structs.NsHeader; -import com.android.net.module.util.structs.PrefixInformationOption; -import com.android.net.module.util.structs.RdnssOption; - -import java.io.IOException; -import java.net.Inet6Address; -import java.nio.ByteBuffer; -import java.util.Map; -import java.util.Objects; -import java.util.Random; - -/** - * ND (RA & NA) responder class useful for tests that require a provisioned IPv6 interface. - * TODO: rename to NdResponder - */ -public class RouterAdvertisementResponder extends PacketResponder { - private static final String TAG = "RouterAdvertisementResponder"; - private static final Inet6Address DNS_SERVER = - (Inet6Address) InetAddresses.parseNumericAddress("2001:4860:4860::64"); - private final TapPacketReader mPacketReader; - // Maps IPv6 address to MacAddress and isRouter boolean. - private final Map<Inet6Address, Pair<MacAddress, Boolean>> mNeighborMap = new ArrayMap<>(); - private final IpPrefix mPrefix; - - public RouterAdvertisementResponder(TapPacketReader packetReader, IpPrefix prefix) { - super(packetReader, RouterAdvertisementResponder::isRsOrNs, TAG); - mPacketReader = packetReader; - mPrefix = Objects.requireNonNull(prefix); - } - - public RouterAdvertisementResponder(TapPacketReader packetReader) { - this(packetReader, makeRandomPrefix()); - } - - private static IpPrefix makeRandomPrefix() { - final byte[] prefixBytes = new IpPrefix("2001:db8::/64").getAddress().getAddress(); - final Random r = new Random(); - for (int i = 4; i < 8; i++) { - prefixBytes[i] = (byte) r.nextInt(); - } - return new IpPrefix(prefixBytes, 64); - } - - /** Returns true if the packet is a router solicitation or neighbor solicitation message. */ - private static boolean isRsOrNs(byte[] packet) { - final ByteBuffer buffer = ByteBuffer.wrap(packet); - final EthernetHeader ethHeader = Struct.parse(EthernetHeader.class, buffer); - if (ethHeader.etherType != ETHER_TYPE_IPV6) { - return false; - } - final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class, buffer); - if (ipv6Header.nextHeader != IPPROTO_ICMPV6) { - return false; - } - final Icmpv6Header icmpv6Header = Struct.parse(Icmpv6Header.class, buffer); - return icmpv6Header.type == ICMPV6_ROUTER_SOLICITATION - || icmpv6Header.type == ICMPV6_NEIGHBOR_SOLICITATION; - } - - /** - * Adds a new router to be advertised. - * @param mac the mac address of the router. - * @param ip the link-local address of the router. - */ - public void addRouterEntry(MacAddress mac, Inet6Address ip) { - mNeighborMap.put(ip, new Pair<>(mac, true)); - } - - /** - * Adds a new neighbor to be advertised. - * @param mac the mac address of the neighbor. - * @param ip the link-local address of the neighbor. - */ - public void addNeighborEntry(MacAddress mac, Inet6Address ip) { - mNeighborMap.put(ip, new Pair<>(mac, false)); - } - - /** - * @return the prefix that is announced in the Router Advertisements sent by this object. - */ - public IpPrefix getPrefix() { - return mPrefix; - } - - private ByteBuffer buildPrefixOption() { - return PrefixInformationOption.build( - mPrefix, (byte) (PIO_FLAG_ON_LINK | PIO_FLAG_AUTONOMOUS), - 3600 /* valid lifetime */, 3600 /* preferred lifetime */); - } - - private ByteBuffer buildRdnssOption() { - return RdnssOption.build(3600/*lifetime, must be at least 120*/, DNS_SERVER); - } - - private ByteBuffer buildSllaOption(MacAddress srcMac) { - return LlaOption.build((byte) ICMPV6_ND_OPTION_SLLA, srcMac); - } - - private ByteBuffer buildRaPacket(MacAddress srcMac, MacAddress dstMac, Inet6Address srcIp) { - return Ipv6Utils.buildRaPacket(srcMac, dstMac, srcIp, IPV6_ADDR_ALL_NODES_MULTICAST, - (byte) 0 /*M=0, O=0*/, 3600 /*lifetime*/, 0 /*reachableTime, unspecified*/, - 0/*retransTimer, unspecified*/, buildPrefixOption(), buildRdnssOption(), - buildSllaOption(srcMac)); - } - - private static void sendResponse(TapPacketReader reader, ByteBuffer buffer) { - try { - reader.sendResponse(buffer); - } catch (IOException e) { - // Throwing an exception here will crash the test process. Let's stick to logging, as - // the test will fail either way. - Log.e(TAG, "Failed to send buffer", e); - } - } - - private void replyToRouterSolicitation(TapPacketReader reader, MacAddress dstMac) { - for (Map.Entry<Inet6Address, Pair<MacAddress, Boolean>> it : mNeighborMap.entrySet()) { - final boolean isRouter = it.getValue().second; - if (!isRouter) { - continue; - } - final ByteBuffer raResponse = buildRaPacket(it.getValue().first, dstMac, it.getKey()); - sendResponse(reader, raResponse); - } - } - - private void replyToNeighborSolicitation(TapPacketReader reader, MacAddress dstMac, - Inet6Address dstIp, Inet6Address targetIp) { - final Pair<MacAddress, Boolean> neighbor = mNeighborMap.get(targetIp); - if (neighbor == null) { - return; - } - - final MacAddress srcMac = neighbor.first; - final boolean isRouter = neighbor.second; - int flags = NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE; - if (isRouter) { - flags |= NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER; - } - - final ByteBuffer tlla = LlaOption.build((byte) ICMPV6_ND_OPTION_TLLA, srcMac); - final ByteBuffer naResponse = Ipv6Utils.buildNaPacket(srcMac, dstMac, targetIp, dstIp, - flags, targetIp, tlla); - sendResponse(reader, naResponse); - } - - @Override - protected void replyToPacket(byte[] packet, TapPacketReader reader) { - final ByteBuffer buf = ByteBuffer.wrap(packet); - // Messages are filtered by parent class, so it is safe to assume that packet is either an - // RS or NS. - final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf); - final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf); - final Icmpv6Header icmpv6Header = Struct.parse(Icmpv6Header.class, buf); - - if (icmpv6Header.type == ICMPV6_ROUTER_SOLICITATION) { - replyToRouterSolicitation(reader, ethHdr.srcMac); - } else if (icmpv6Header.type == ICMPV6_NEIGHBOR_SOLICITATION) { - final NsHeader nsHeader = Struct.parse(NsHeader.class, buf); - replyToNeighborSolicitation(reader, ethHdr.srcMac, ipv6Hdr.srcIp, nsHeader.target); - } - } -} diff --git a/common/testutils/devicetests/com/android/testutils/TapPacketReader.java b/common/testutils/devicetests/com/android/testutils/TapPacketReader.java deleted file mode 100644 index b25b9f21..00000000 --- a/common/testutils/devicetests/com/android/testutils/TapPacketReader.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils; - -import android.os.Handler; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.net.module.util.ArrayTrackRecord; -import com.android.net.module.util.PacketReader; - -import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.function.Predicate; - -import kotlin.Lazy; -import kotlin.LazyKt; - -/** - * A packet reader that runs on a TAP interface. - * - * It also implements facilities to reply to received packets. - */ -public class TapPacketReader extends PacketReader { - private final FileDescriptor mTapFd; - private final ArrayTrackRecord<byte[]> mReceivedPackets = new ArrayTrackRecord<>(); - private final Lazy<ArrayTrackRecord<byte[]>.ReadHead> mReadHead = - LazyKt.lazy(mReceivedPackets::newReadHead); - - public TapPacketReader(Handler h, FileDescriptor tapFd, int maxPacketSize) { - super(h, maxPacketSize); - mTapFd = tapFd; - } - - - /** - * Attempt to start the FdEventsReader on its handler thread. - * - * As opposed to {@link android.net.util.FdEventsReader#start()}, this method will not report - * failure to start, so it is only appropriate in tests that will fail later if that happens. - */ - public void startAsyncForTest() { - getHandler().post(this::start); - } - - @Override - protected FileDescriptor createFd() { - return mTapFd; - } - - @Override - protected void handlePacket(byte[] recvbuf, int length) { - final byte[] newPacket = Arrays.copyOf(recvbuf, length); - if (!mReceivedPackets.add(newPacket)) { - throw new AssertionError("More than " + Integer.MAX_VALUE + " packets outstanding!"); - } - } - - /** - * @deprecated This method does not actually "pop" (which generally means the last packet). - * Use {@link #poll(long)}, which has the same behavior, instead. - */ - @Nullable - @Deprecated - public byte[] popPacket(long timeoutMs) { - return poll(timeoutMs); - } - - /** - * @deprecated This method does not actually "pop" (which generally means the last packet). - * Use {@link #poll(long, Predicate)}, which has the same behavior, instead. - */ - @Nullable - @Deprecated - public byte[] popPacket(long timeoutMs, @NonNull Predicate<byte[]> filter) { - return poll(timeoutMs, filter); - } - - /** - * Get the next packet that was received on the interface. - */ - @Nullable - public byte[] poll(long timeoutMs) { - return mReadHead.getValue().poll(timeoutMs, packet -> true); - } - - /** - * Get the next packet that was received on the interface and matches the specified filter. - */ - @Nullable - public byte[] poll(long timeoutMs, @NonNull Predicate<byte[]> filter) { - return mReadHead.getValue().poll(timeoutMs, filter::test); - } - - /** - * Get the {@link ArrayTrackRecord} that records all packets received by the reader since its - * creation. - */ - public ArrayTrackRecord<byte[]> getReceivedPackets() { - return mReceivedPackets; - } - - /* - * Send a response on the TAP interface. - * - * The passed ByteBuffer is flipped after use. - * - * @param packet The packet to send. - * @throws IOException if the interface can't be written to. - */ - public void sendResponse(final ByteBuffer packet) throws IOException { - try (FileOutputStream out = new FileOutputStream(mTapFd)) { - byte[] packetBytes = new byte[packet.limit()]; - packet.get(packetBytes); - packet.flip(); // So we can reuse it in the future. - out.write(packetBytes); - } - } -} diff --git a/common/testutils/devicetests/com/android/testutils/TapPacketReaderRule.kt b/common/testutils/devicetests/com/android/testutils/TapPacketReaderRule.kt deleted file mode 100644 index 701666ca..00000000 --- a/common/testutils/devicetests/com/android/testutils/TapPacketReaderRule.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.Manifest.permission.MANAGE_TEST_NETWORKS -import android.net.TestNetworkInterface -import android.net.TestNetworkManager -import android.os.Handler -import android.os.HandlerThread -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement -import kotlin.test.assertFalse -import kotlin.test.fail - -private const val HANDLER_TIMEOUT_MS = 10_000L - -/** - * A [TestRule] that sets up a [TapPacketReader] on a [TestNetworkInterface] for use in the test. - * - * @param maxPacketSize Maximum size of packets read in the [TapPacketReader] buffer. - * @param autoStart Whether to initialize the interface and start the reader automatically for every - * test. If false, each test must either call start() and stop(), or be annotated - * with TapPacketReaderTest before using the reader or interface. - */ -class TapPacketReaderRule @JvmOverloads constructor( - private val maxPacketSize: Int = 1500, - private val autoStart: Boolean = true -) : TestRule { - // Use lateinit as the below members can't be initialized in the rule constructor (the - // InstrumentationRegistry may not be ready), but from the point of view of test cases using - // this rule with autoStart = true, the members are always initialized (in setup/test/teardown): - // tests cases should be able use them directly. - // lateinit also allows getting good exceptions detailing what went wrong if the members are - // referenced before they could be initialized (typically if autoStart is false and the test - // does not call start or use @TapPacketReaderTest). - lateinit var iface: TestNetworkInterface - lateinit var reader: TapPacketReader - - @Volatile - private var readerRunning = false - - /** - * Indicates that the [TapPacketReaderRule] should initialize its [TestNetworkInterface] and - * start the [TapPacketReader] before the test, and tear them down afterwards. - * - * For use when [TapPacketReaderRule] is created with autoStart = false. - */ - annotation class TapPacketReaderTest - - /** - * Initialize the tap interface and start the [TapPacketReader]. - * - * Tests using this method must also call [stop] before exiting. - * @param handler Handler to run the reader on. Callers are responsible for safely terminating - * the handler when the test ends. If null, a handler thread managed by the - * rule will be used. - */ - @JvmOverloads - fun start(handler: Handler? = null) { - if (this::iface.isInitialized) { - fail("${TapPacketReaderRule::class.java.simpleName} was already started") - } - - val ctx = InstrumentationRegistry.getInstrumentation().context - iface = runAsShell(MANAGE_TEST_NETWORKS) { - val tnm = ctx.getSystemService(TestNetworkManager::class.java) - ?: fail("Could not obtain the TestNetworkManager") - tnm.createTapInterface() - } - val usedHandler = handler ?: HandlerThread( - TapPacketReaderRule::class.java.simpleName).apply { start() }.threadHandler - reader = TapPacketReader(usedHandler, iface.fileDescriptor.fileDescriptor, maxPacketSize) - reader.startAsyncForTest() - readerRunning = true - } - - /** - * Stop the [TapPacketReader]. - * - * Tests calling [start] must call this method before exiting. If a handler was specified in - * [start], all messages on that handler must also be processed after calling this method and - * before exiting. - * - * If [start] was not called, calling this method is a no-op. - */ - fun stop() { - // The reader may not be initialized if the test case did not use the rule, even though - // other test cases in the same class may be using it (so test classes may call stop in - // tearDown even if start is not called for all test cases). - if (!this::reader.isInitialized) return - reader.handler.post { - reader.stop() - readerRunning = false - } - } - - override fun apply(base: Statement, description: Description): Statement { - return TapReaderStatement(base, description) - } - - private inner class TapReaderStatement( - private val base: Statement, - private val description: Description - ) : Statement() { - override fun evaluate() { - val shouldStart = autoStart || - description.getAnnotation(TapPacketReaderTest::class.java) != null - if (shouldStart) { - start() - } - - try { - base.evaluate() - } finally { - if (shouldStart) { - stop() - reader.handler.looper.apply { - quitSafely() - thread.join(HANDLER_TIMEOUT_MS) - assertFalse(thread.isAlive, - "HandlerThread did not exit within $HANDLER_TIMEOUT_MS ms") - } - } - - if (this@TapPacketReaderRule::iface.isInitialized) { - iface.fileDescriptor.close() - } - } - - assertFalse(readerRunning, - "stop() was not called, or the provided handler did not process the stop " + - "message before the test ended. If not using autostart, make sure to call " + - "stop() after the test. If a handler is specified in start(), make sure all " + - "messages are processed after calling stop(), before quitting (for example " + - "by using HandlerThread#quitSafely and HandlerThread#join).") - } - } -}
\ No newline at end of file diff --git a/common/testutils/devicetests/com/android/testutils/TestBpfMap.java b/common/testutils/devicetests/com/android/testutils/TestBpfMap.java deleted file mode 100644 index 733bd980..00000000 --- a/common/testutils/devicetests/com/android/testutils/TestBpfMap.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils; - -import android.system.ErrnoException; - -import androidx.annotation.NonNull; - -import com.android.net.module.util.IBpfMap; -import com.android.net.module.util.Struct; - -import java.io.IOException; -import java.util.Iterator; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - -/** - * - * Fake BPF map class for tests that have no privilege to access real BPF maps. TestBpfMap does not - * load JNI and all member functions do not access real BPF maps. - * - * Implements IBpfMap so that any class using IBpfMap can use this class in its tests. - * - * @param <K> the key type - * @param <V> the value type - */ -public class TestBpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V> { - private final ConcurrentHashMap<K, V> mMap = new ConcurrentHashMap<>(); - - // TODO: Remove this constructor - public TestBpfMap(final Class<K> key, final Class<V> value) { - } - - @Override - public void forEach(ThrowingBiConsumer<K, V> action) throws ErrnoException { - // TODO: consider using mocked #getFirstKey and #getNextKey to iterate. It helps to - // implement the entry deletion in the iteration if required. - for (Map.Entry<K, V> entry : mMap.entrySet()) { - action.accept(entry.getKey(), entry.getValue()); - } - } - - @Override - public void updateEntry(K key, V value) throws ErrnoException { - mMap.put(key, value); - } - - @Override - public void insertEntry(K key, V value) throws ErrnoException, - IllegalArgumentException { - // The entry is created if and only if it doesn't exist. See BpfMap#insertEntry. - if (mMap.get(key) != null) { - throw new IllegalArgumentException(key + " already exist"); - } - mMap.put(key, value); - } - - @Override - public void replaceEntry(K key, V value) throws ErrnoException, NoSuchElementException { - if (!mMap.containsKey(key)) throw new NoSuchElementException(); - mMap.put(key, value); - } - - @Override - public boolean insertOrReplaceEntry(K key, V value) throws ErrnoException { - // Returns true if inserted, false if replaced. - boolean ret = !mMap.containsKey(key); - mMap.put(key, value); - return ret; - } - - @Override - public boolean deleteEntry(Struct key) throws ErrnoException { - return mMap.remove(key) != null; - } - - @Override - public boolean isEmpty() throws ErrnoException { - return mMap.isEmpty(); - } - - @Override - public K getNextKey(@NonNull K key) { - // Expensive, but since this is only for tests... - Iterator<K> it = mMap.keySet().iterator(); - while (it.hasNext()) { - if (Objects.equals(it.next(), key)) { - return it.hasNext() ? it.next() : null; - } - } - return null; - } - - @Override - public K getFirstKey() { - for (K key : mMap.keySet()) { - return key; - } - return null; - } - - @Override - public boolean containsKey(@NonNull K key) throws ErrnoException { - return mMap.containsKey(key); - } - - @Override - public V getValue(@NonNull K key) throws ErrnoException { - // Return value for a given key. Otherwise, return null without an error ENOENT. - // BpfMap#getValue treats that the entry is not found as no error. - return mMap.get(key); - } - - @Override - public void clear() throws ErrnoException { - // TODO: consider using mocked #getFirstKey and #deleteEntry to implement. - mMap.clear(); - } - - @Override - public void close() throws IOException { - } -} diff --git a/common/testutils/devicetests/com/android/testutils/TestDnsServer.kt b/common/testutils/devicetests/com/android/testutils/TestDnsServer.kt deleted file mode 100644 index e1b771b2..00000000 --- a/common/testutils/devicetests/com/android/testutils/TestDnsServer.kt +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.net.Network -import android.util.Log -import com.android.internal.annotations.GuardedBy -import com.android.internal.annotations.VisibleForTesting -import com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE -import com.android.net.module.util.DnsPacket -import java.net.DatagramPacket -import java.net.DatagramSocket -import java.net.InetAddress -import java.net.InetSocketAddress -import java.net.SocketAddress -import java.net.SocketException -import java.util.ArrayList - -private const val TAG = "TestDnsServer" -private const val VDBG = true -@VisibleForTesting(visibility = PRIVATE) -const val MAX_BUF_SIZE = 8192 - -/** - * A simple implementation of Dns Server that can be bound on specific address and Network. - * - * The caller should use start() to make the server start a new thread to receive DNS queries - * on the bound address, [isAlive] to check status, and stop() for stopping. - * The server allows user to manipulate the records to be answered through - * [setAnswer] at runtime. - * - * This server runs on its own thread. Please make sure writing the query to the socket - * happens-after using [setAnswer] to guarantee the correct answer is returned. If possible, - * use [setAnswer] before calling [start] for simplicity. - */ -class TestDnsServer(network: Network, addr: InetSocketAddress) { - enum class Status { - NOT_STARTED, STARTED, STOPPED - } - @GuardedBy("thread") - private var status: Status = Status.NOT_STARTED - private val thread = ReceivingThread() - private val socket = DatagramSocket(addr).also { network.bindSocket(it) } - private val ansProvider = DnsAnswerProvider() - - // The buffer to store the received packet. They are being reused for - // efficiency and it's fine because they are only ever accessed - // on the server thread in a sequential manner. - private val buffer = ByteArray(MAX_BUF_SIZE) - private val packet = DatagramPacket(buffer, buffer.size) - - fun setAnswer(hostname: String, answer: List<InetAddress>) = - ansProvider.setAnswer(hostname, answer) - - private fun processPacket() { - // Blocking read and try construct a DnsQueryPacket object. - socket.receive(packet) - val q = DnsQueryPacket(packet.data) - handleDnsQuery(q, packet.socketAddress) - } - - // TODO: Add support to reply some error with a DNS reply packet with failure RCODE. - private fun handleDnsQuery(q: DnsQueryPacket, src: SocketAddress) { - val queryRecords = q.queryRecords - if (queryRecords.size != 1) { - throw IllegalArgumentException( - "Expected one dns query record but got ${queryRecords.size}" - ) - } - val answerRecords = queryRecords[0].let { ansProvider.getAnswer(it.dName, it.nsType) } - - if (VDBG) { - Log.v(TAG, "handleDnsPacket: " + - queryRecords.map { "${it.dName},${it.nsType}" }.joinToString() + - " ansCount=${answerRecords.size} socketAddress=$src") - } - - val bytes = q.getAnswerPacket(answerRecords).bytes - val reply = DatagramPacket(bytes, bytes.size, src) - socket.send(reply) - } - - fun start() { - synchronized(thread) { - if (status != Status.NOT_STARTED) { - throw IllegalStateException("unexpected status: $status") - } - thread.start() - status = Status.STARTED - } - } - fun stop() { - synchronized(thread) { - if (status != Status.STARTED) { - throw IllegalStateException("unexpected status: $status") - } - // The thread needs to be interrupted before closing the socket to prevent a data - // race where the thread tries to read from the socket while it's being closed. - // DatagramSocket is not thread-safe and running both concurrently can end up in - // getPort() returning -1 after it's been checked not to, resulting in a crash by - // IllegalArgumentException inside the DatagramSocket implementation. - thread.interrupt() - socket.close() - thread.join() - status = Status.STOPPED - } - } - val isAlive get() = thread.isAlive - val port get() = socket.localPort - - inner class ReceivingThread : Thread() { - override fun run() { - while (!interrupted() && !socket.isClosed) { - try { - processPacket() - } catch (e: InterruptedException) { - // The caller terminated the server, exit. - break - } catch (e: SocketException) { - // The caller terminated the server, exit. - break - } - } - Log.i(TAG, "exiting socket={$socket}") - } - } - - @VisibleForTesting(visibility = PRIVATE) - class DnsQueryPacket : DnsPacket { - constructor(data: ByteArray) : super(data) - constructor(header: DnsHeader, qd: List<DnsRecord>, an: List<DnsRecord>) : - super(header, qd, an) - - init { - if (mHeader.isResponse) { - throw ParseException("Not a query packet") - } - } - - val queryRecords: List<DnsRecord> - get() = mRecords[QDSECTION] - - fun getAnswerPacket(ar: List<DnsRecord>): DnsAnswerPacket { - // Set QR bit of flag to 1 for response packet according to RFC 1035 section 4.1.1. - val flags = 1 shl 15 - val qr = ArrayList(mRecords[QDSECTION]) - // Copy the query packet header id to the answer packet as RFC 1035 section 4.1.1. - val header = DnsHeader(mHeader.id, flags, qr.size, ar.size) - return DnsAnswerPacket(header, qr, ar) - } - } - - class DnsAnswerPacket : DnsPacket { - constructor(header: DnsHeader, qr: List<DnsRecord>, ar: List<DnsRecord>) : - super(header, qr, ar) - @VisibleForTesting(visibility = PRIVATE) - constructor(bytes: ByteArray) : super(bytes) - } -} diff --git a/common/testutils/devicetests/com/android/testutils/TestHttpServer.kt b/common/testutils/devicetests/com/android/testutils/TestHttpServer.kt deleted file mode 100644 index 740bf63a..00000000 --- a/common/testutils/devicetests/com/android/testutils/TestHttpServer.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.net.Uri -import com.android.net.module.util.ArrayTrackRecord -import fi.iki.elonen.NanoHTTPD -import java.io.IOException - -/** - * A minimal HTTP server running on a random available port. - * - * @param host The host to listen to, or null to listen on all hosts - */ -class TestHttpServer(host: String? = null) : NanoHTTPD(host, 0 /* auto-select the port */) { - // Map of URL path -> HTTP response code - private val responses = HashMap<Request, Response>() - - /** - * A record of all requests received by the server since it was started. - */ - val requestsRecord = ArrayTrackRecord<Request>() - - /** - * A request received by the test server. - */ - data class Request( - val path: String, - val method: Method = Method.GET, - val queryParameters: String = "" - ) { - /** - * Returns whether the specified [Uri] matches parameters of this request. - */ - fun matches(uri: Uri) = (uri.path ?: "") == path && (uri.query ?: "") == queryParameters - } - - /** - * Add a response for GET requests with the path and query parameters of the specified [Uri]. - */ - fun addResponse( - uri: Uri, - statusCode: Response.IStatus, - headers: Map<String, String>? = null, - content: String = "" - ) { - addResponse(Request(uri.path - ?: "", Method.GET, uri.query ?: ""), - statusCode, headers, content) - } - - /** - * Add a response for the given request. - */ - fun addResponse( - request: Request, - statusCode: Response.IStatus, - headers: Map<String, String>? = null, - content: String = "" - ) { - val response = newFixedLengthResponse(statusCode, "text/plain", content) - headers?.forEach { - (key, value) -> response.addHeader(key, value) - } - responses[request] = response - } - - override fun serve(session: IHTTPSession): Response { - val request = Request(session.uri - ?: "", session.method, session.queryParameterString ?: "") - requestsRecord.add(request) - - // For PUT and POST, call parseBody to read InputStream before responding. - if (Method.PUT == session.method || Method.POST == session.method) { - try { - session.parseBody(HashMap()) - } catch (e: Exception) { - when (e) { - is IOException, is ResponseException -> e.toResponse() - else -> throw e - } - } - } - - // Default response is a 404 - return responses[request] ?: super.serve(session) - } - - fun Exception.toResponse() = - newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", this.toString()) -} diff --git a/common/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt b/common/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt deleted file mode 100644 index 84fb47bc..00000000 --- a/common/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.content.Context -import android.net.ConnectivityManager -import android.net.ConnectivityManager.NetworkCallback -import android.net.LinkAddress -import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkRequest -import android.net.LinkProperties -import android.net.TestNetworkInterface -import android.net.TestNetworkManager -import android.os.Binder -import android.os.Build -import androidx.annotation.RequiresApi -import com.android.modules.utils.build.SdkLevel.isAtLeastR -import com.android.modules.utils.build.SdkLevel.isAtLeastS -import java.util.concurrent.CompletableFuture -import java.util.concurrent.TimeUnit -import kotlin.test.assertTrue - -/** - * Create a test network based on a TUN interface with a LinkAddress. - * - * TODO: remove this function after fixing all the callers to use a list of LinkAddresses. - * This method will block until the test network is available. Requires - * [android.Manifest.permission.CHANGE_NETWORK_STATE] and - * [android.Manifest.permission.MANAGE_TEST_NETWORKS]. - */ -fun initTestNetwork( - context: Context, - interfaceAddr: LinkAddress, - setupTimeoutMs: Long = 10_000L -): TestNetworkTracker { - return initTestNetwork(context, listOf(interfaceAddr), setupTimeoutMs) -} - -/** - * Create a test network based on a TUN interface with a LinkAddress list. - * - * This method will block until the test network is available. Requires - * [android.Manifest.permission.CHANGE_NETWORK_STATE] and - * [android.Manifest.permission.MANAGE_TEST_NETWORKS]. - */ -fun initTestNetwork( - context: Context, - linkAddrs: List<LinkAddress>, - setupTimeoutMs: Long = 10_000L -): TestNetworkTracker { - return initTestNetwork(context, linkAddrs, lp = null, setupTimeoutMs = setupTimeoutMs) -} - -/** - * Create a test network based on a TUN interface - * - * This method will block until the test network is available. Requires - * [android.Manifest.permission.CHANGE_NETWORK_STATE] and - * [android.Manifest.permission.MANAGE_TEST_NETWORKS]. - * - * This is only usable starting from R as [TestNetworkManager] has no support for specifying - * LinkProperties on Q. - */ -@RequiresApi(Build.VERSION_CODES.R) -fun initTestNetwork( - context: Context, - lp: LinkProperties, - setupTimeoutMs: Long = 10_000L -): TestNetworkTracker { - return initTestNetwork(context, lp.linkAddresses, lp, setupTimeoutMs) -} - -private fun initTestNetwork( - context: Context, - linkAddrs: List<LinkAddress>, - lp: LinkProperties?, - setupTimeoutMs: Long = 10_000L -): TestNetworkTracker { - val tnm = context.getSystemService(TestNetworkManager::class.java)!! - val iface = if (isAtLeastS()) tnm.createTunInterface(linkAddrs) - else tnm.createTunInterface(linkAddrs.toTypedArray()) - val lpWithIface = if (lp == null) null else LinkProperties(lp).apply { - interfaceName = iface.interfaceName - } - return TestNetworkTracker(context, iface, tnm, lpWithIface, setupTimeoutMs) -} - -/** - * Utility class to create and track test networks. - * - * This class is not thread-safe. - */ -class TestNetworkTracker internal constructor( - val context: Context, - val iface: TestNetworkInterface, - val tnm: TestNetworkManager, - val lp: LinkProperties?, - setupTimeoutMs: Long -) : TestableNetworkCallback.HasNetwork { - private val cm = context.getSystemService(ConnectivityManager::class.java)!! - private val binder = Binder() - - private val networkCallback: NetworkCallback - override val network: Network - val testIface: TestNetworkInterface - - init { - val networkFuture = CompletableFuture<Network>() - val networkRequest = NetworkRequest.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_TEST) - // Test networks do not have NOT_VPN or TRUSTED capabilities by default - .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) - .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) - .setNetworkSpecifier(CompatUtil.makeTestNetworkSpecifier(iface.interfaceName)) - .build() - networkCallback = object : NetworkCallback() { - override fun onAvailable(network: Network) { - networkFuture.complete(network) - } - } - cm.requestNetwork(networkRequest, networkCallback) - - network = try { - if (lp != null) { - assertTrue(isAtLeastR(), "Cannot specify TestNetwork LinkProperties before R") - tnm.setupTestNetwork(lp, true /* isMetered */, binder) - } else { - tnm.setupTestNetwork(iface.interfaceName, binder) - } - networkFuture.get(setupTimeoutMs, TimeUnit.MILLISECONDS) - } catch (e: Throwable) { - cm.unregisterNetworkCallback(networkCallback) - throw e - } - - testIface = iface - } - - fun teardown() { - cm.unregisterNetworkCallback(networkCallback) - tnm.teardownTestNetwork(network) - } -} diff --git a/common/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt b/common/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt deleted file mode 100644 index f571f64f..00000000 --- a/common/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:JvmName("TestPermissionUtil") - -package com.android.testutils - -import androidx.test.platform.app.InstrumentationRegistry -import com.android.modules.utils.build.SdkLevel -import com.android.testutils.FunctionalUtils.ThrowingRunnable -import com.android.testutils.FunctionalUtils.ThrowingSupplier - -/** - * Run the specified [task] with the specified [permissions] obtained through shell - * permission identity. - * - * Passing in an empty list of permissions can grant all shell permissions, but this is - * discouraged as it also causes the process to temporarily lose non-shell permissions. - */ -fun <T> runAsShell(vararg permissions: String, task: () -> T): T { - val autom = InstrumentationRegistry.getInstrumentation().uiAutomation - - // Calls to adoptShellPermissionIdentity do not nest, and dropShellPermissionIdentity drops all - // permissions. Thus, nesting calls will almost certainly cause test bugs, On S+, where we can - // detect this, refuse to do it. - // - // TODO: when R is deprecated, we could try to make this work instead. - // - Get the list of previously-adopted permissions. - // - Adopt the union of the previously-adopted and newly-requested permissions. - // - Run the task. - // - Adopt the previously-adopted permissions, dropping the ones just adopted. - // - // This would allow tests (and utility classes, such as the TestCarrierConfigReceiver attempted - // in aosp/2106007) to call runAsShell even within a test that has already adopted permissions. - if (SdkLevel.isAtLeastS() && !autom.getAdoptedShellPermissions().isNullOrEmpty()) { - throw IllegalStateException("adoptShellPermissionIdentity calls must not be nested") - } - - autom.adoptShellPermissionIdentity(*permissions) - try { - return task() - } finally { - autom.dropShellPermissionIdentity() - } -} - -/** - * Convenience overload of [runAsShell] that uses a [ThrowingSupplier] for Java callers, when - * only one/two/three permissions are needed. - */ -@JvmOverloads -fun <T> runAsShell( - perm1: String, - perm2: String = "", - perm3: String = "", - supplier: ThrowingSupplier<T> -): T = runAsShell(*getNonEmptyVarargs(perm1, perm2, perm3)) { supplier.get() } - -/** - * Convenience overload of [runAsShell] that uses a [ThrowingRunnable] for Java callers, when - * only one/two/three permissions are needed. - */ -@JvmOverloads -fun runAsShell( - perm1: String, - perm2: String = "", - perm3: String = "", - runnable: ThrowingRunnable -): Unit = runAsShell(*getNonEmptyVarargs(perm1, perm2, perm3)) { runnable.run() } - -/** - * Get an array containing the first consecutive non-empty arguments out of three arguments. - * - * The first argument is assumed to be non-empty. - */ -private fun getNonEmptyVarargs(arg1: String, arg2: String, arg3: String): Array<String> { - return when { - arg2 == "" -> arrayOf(arg1) - arg3 == "" -> arrayOf(arg1, arg2) - else -> arrayOf(arg1, arg2, arg3) - } -}
\ No newline at end of file diff --git a/common/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt b/common/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt deleted file mode 100644 index 8dc1bc45..00000000 --- a/common/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils; - -import android.content.Context -import android.net.KeepalivePacketData -import android.net.LinkProperties -import android.net.NetworkAgent -import android.net.NetworkAgentConfig -import android.net.NetworkCapabilities -import android.net.NetworkProvider -import android.net.QosFilter -import android.net.Uri -import android.os.Looper -import com.android.net.module.util.ArrayTrackRecord -import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnAddKeepalivePacketFilter -import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnAutomaticReconnectDisabled -import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnBandwidthUpdateRequested -import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnDscpPolicyStatusUpdated -import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated -import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkDestroyed -import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkUnwanted -import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnRegisterQosCallback -import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnRemoveKeepalivePacketFilter -import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnSaveAcceptUnvalidated -import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnSignalStrengthThresholdsUpdated -import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnStartSocketKeepalive -import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnStopSocketKeepalive -import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnUnregisterQosCallback -import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus -import java.time.Duration -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.assertTrue -import org.junit.Assert.assertArrayEquals - -// Any legal score (0~99) for the test network would do, as it is going to be kept up by the -// requests filed by the test and should never match normal internet requests. 70 is the default -// score of Ethernet networks, it's as good a value as any other. -private const val TEST_NETWORK_SCORE = 70 - -private class Provider(context: Context, looper: Looper) : - NetworkProvider(context, looper, "NetworkAgentTest NetworkProvider") - -public open class TestableNetworkAgent( - context: Context, - looper: Looper, - val nc: NetworkCapabilities, - val lp: LinkProperties, - conf: NetworkAgentConfig -) : NetworkAgent(context, looper, TestableNetworkAgent::class.java.simpleName /* tag */, - nc, lp, TEST_NETWORK_SCORE, conf, Provider(context, looper)) { - - val DEFAULT_TIMEOUT_MS = 5000L - - val history = ArrayTrackRecord<CallbackEntry>().newReadHead() - - sealed class CallbackEntry { - object OnBandwidthUpdateRequested : CallbackEntry() - object OnNetworkUnwanted : CallbackEntry() - data class OnAddKeepalivePacketFilter( - val slot: Int, - val packet: KeepalivePacketData - ) : CallbackEntry() - data class OnRemoveKeepalivePacketFilter(val slot: Int) : CallbackEntry() - data class OnStartSocketKeepalive( - val slot: Int, - val interval: Int, - val packet: KeepalivePacketData - ) : CallbackEntry() - data class OnStopSocketKeepalive(val slot: Int) : CallbackEntry() - data class OnSaveAcceptUnvalidated(val accept: Boolean) : CallbackEntry() - object OnAutomaticReconnectDisabled : CallbackEntry() - data class OnValidationStatus(val status: Int, val uri: Uri?) : CallbackEntry() - data class OnSignalStrengthThresholdsUpdated(val thresholds: IntArray) : CallbackEntry() - object OnNetworkCreated : CallbackEntry() - object OnNetworkDestroyed : CallbackEntry() - data class OnDscpPolicyStatusUpdated(val policyId: Int, val status: Int) : CallbackEntry() - data class OnRegisterQosCallback( - val callbackId: Int, - val filter: QosFilter - ) : CallbackEntry() - data class OnUnregisterQosCallback(val callbackId: Int) : CallbackEntry() - } - - override fun onBandwidthUpdateRequested() { - history.add(OnBandwidthUpdateRequested) - } - - override fun onNetworkUnwanted() { - history.add(OnNetworkUnwanted) - } - - override fun onAddKeepalivePacketFilter(slot: Int, packet: KeepalivePacketData) { - history.add(OnAddKeepalivePacketFilter(slot, packet)) - } - - override fun onRemoveKeepalivePacketFilter(slot: Int) { - history.add(OnRemoveKeepalivePacketFilter(slot)) - } - - override fun onStartSocketKeepalive( - slot: Int, - interval: Duration, - packet: KeepalivePacketData - ) { - history.add(OnStartSocketKeepalive(slot, interval.seconds.toInt(), packet)) - } - - override fun onStopSocketKeepalive(slot: Int) { - history.add(OnStopSocketKeepalive(slot)) - } - - override fun onSaveAcceptUnvalidated(accept: Boolean) { - history.add(OnSaveAcceptUnvalidated(accept)) - } - - override fun onAutomaticReconnectDisabled() { - history.add(OnAutomaticReconnectDisabled) - } - - override fun onSignalStrengthThresholdsUpdated(thresholds: IntArray) { - history.add(OnSignalStrengthThresholdsUpdated(thresholds)) - } - - fun expectSignalStrengths(thresholds: IntArray? = intArrayOf()) { - expectCallback<OnSignalStrengthThresholdsUpdated>().let { - assertArrayEquals(thresholds, it.thresholds) - } - } - - override fun onQosCallbackRegistered(qosCallbackId: Int, filter: QosFilter) { - history.add(OnRegisterQosCallback(qosCallbackId, filter)) - } - - override fun onQosCallbackUnregistered(qosCallbackId: Int) { - history.add(OnUnregisterQosCallback(qosCallbackId)) - } - - override fun onValidationStatus(status: Int, uri: Uri?) { - history.add(OnValidationStatus(status, uri)) - } - - override fun onNetworkCreated() { - history.add(OnNetworkCreated) - } - - override fun onNetworkDestroyed() { - history.add(OnNetworkDestroyed) - } - - override fun onDscpPolicyStatusUpdated(policyId: Int, status: Int) { - history.add(OnDscpPolicyStatusUpdated(policyId, status)) - } - - // Expects the initial validation event that always occurs immediately after registering - // a NetworkAgent whose network does not require validation (which test networks do - // not, since they lack the INTERNET capability). It always contains the default argument - // for the URI. - fun expectValidationBypassedStatus() = expectCallback<OnValidationStatus>().let { - assertEquals(it.status, VALID_NETWORK) - // The returned Uri is parsed from the empty string, which means it's an - // instance of the (private) Uri.StringUri. There are no real good ways - // to check this, the least bad is to just convert it to a string and - // make sure it's empty. - assertEquals("", it.uri.toString()) - } - - inline fun <reified T : CallbackEntry> expectCallback(): T { - val foundCallback = history.poll(DEFAULT_TIMEOUT_MS) - assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback") - return foundCallback - } - - inline fun <reified T : CallbackEntry> expectCallback(valid: (T) -> Boolean) { - val foundCallback = history.poll(DEFAULT_TIMEOUT_MS) - assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback") - assertTrue(valid(foundCallback), "Unexpected callback : $foundCallback") - } - - inline fun <reified T : CallbackEntry> eventuallyExpect() = - history.poll(DEFAULT_TIMEOUT_MS) { it is T }.also { - assertNotNull(it, "Callback ${T::class} not received") - } as T - - fun assertNoCallback() { - assertTrue(waitForIdle(DEFAULT_TIMEOUT_MS), - "Handler didn't became idle after ${DEFAULT_TIMEOUT_MS}ms") - assertNull(history.peek()) - } -} diff --git a/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt deleted file mode 100644 index df9c61ae..00000000 --- a/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt +++ /dev/null @@ -1,569 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.net.ConnectivityManager.NetworkCallback -import android.net.LinkProperties -import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED -import android.util.Log -import com.android.net.module.util.ArrayTrackRecord -import com.android.testutils.RecorderCallback.CallbackEntry.Available -import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus -import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatusInt -import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged -import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged -import com.android.testutils.RecorderCallback.CallbackEntry.Losing -import com.android.testutils.RecorderCallback.CallbackEntry.Lost -import com.android.testutils.RecorderCallback.CallbackEntry.Resumed -import com.android.testutils.RecorderCallback.CallbackEntry.Suspended -import com.android.testutils.RecorderCallback.CallbackEntry.Unavailable -import kotlin.reflect.KClass -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.fail - -object NULL_NETWORK : Network(-1) -object ANY_NETWORK : Network(-2) -fun anyNetwork() = ANY_NETWORK - -open class RecorderCallback private constructor( - private val backingRecord: ArrayTrackRecord<CallbackEntry> -) : NetworkCallback() { - public constructor() : this(ArrayTrackRecord()) - protected constructor(src: RecorderCallback?) : this(src?.backingRecord ?: ArrayTrackRecord()) - - private val TAG = this::class.simpleName - - sealed class CallbackEntry { - // To get equals(), hashcode(), componentN() etc for free, the child classes of - // this class are data classes. But while data classes can inherit from other classes, - // they may only have visible members in the constructors, so they couldn't declare - // a constructor with a non-val arg to pass to CallbackEntry. Instead, force all - // subclasses to implement a `network' property, which can be done in a data class - // constructor by specifying override. - abstract val network: Network - - data class Available(override val network: Network) : CallbackEntry() - data class CapabilitiesChanged( - override val network: Network, - val caps: NetworkCapabilities - ) : CallbackEntry() - data class LinkPropertiesChanged( - override val network: Network, - val lp: LinkProperties - ) : CallbackEntry() - data class Suspended(override val network: Network) : CallbackEntry() - data class Resumed(override val network: Network) : CallbackEntry() - data class Losing(override val network: Network, val maxMsToLive: Int) : CallbackEntry() - data class Lost(override val network: Network) : CallbackEntry() - data class Unavailable private constructor( - override val network: Network - ) : CallbackEntry() { - constructor() : this(NULL_NETWORK) - } - data class BlockedStatus( - override val network: Network, - val blocked: Boolean - ) : CallbackEntry() - data class BlockedStatusInt( - override val network: Network, - val reason: Int - ) : CallbackEntry() - // Convenience constants for expecting a type - companion object { - @JvmField - val AVAILABLE = Available::class - @JvmField - val NETWORK_CAPS_UPDATED = CapabilitiesChanged::class - @JvmField - val LINK_PROPERTIES_CHANGED = LinkPropertiesChanged::class - @JvmField - val SUSPENDED = Suspended::class - @JvmField - val RESUMED = Resumed::class - @JvmField - val LOSING = Losing::class - @JvmField - val LOST = Lost::class - @JvmField - val UNAVAILABLE = Unavailable::class - @JvmField - val BLOCKED_STATUS = BlockedStatus::class - @JvmField - val BLOCKED_STATUS_INT = BlockedStatusInt::class - } - } - - val history = backingRecord.newReadHead() - val mark get() = history.mark - - override fun onAvailable(network: Network) { - Log.d(TAG, "onAvailable $network") - history.add(Available(network)) - } - - // PreCheck is not used in the tests today. For backward compatibility with existing tests that - // expect the callbacks not to record this, do not listen to PreCheck here. - - override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) { - Log.d(TAG, "onCapabilitiesChanged $network $caps") - history.add(CapabilitiesChanged(network, caps)) - } - - override fun onLinkPropertiesChanged(network: Network, lp: LinkProperties) { - Log.d(TAG, "onLinkPropertiesChanged $network $lp") - history.add(LinkPropertiesChanged(network, lp)) - } - - override fun onBlockedStatusChanged(network: Network, blocked: Boolean) { - Log.d(TAG, "onBlockedStatusChanged $network $blocked") - history.add(BlockedStatus(network, blocked)) - } - - // Cannot do: - // fun onBlockedStatusChanged(network: Network, blocked: Int) { - // because on S, that needs to be "override fun", and on R, that cannot be "override fun". - override fun onNetworkSuspended(network: Network) { - Log.d(TAG, "onNetworkSuspended $network $network") - history.add(Suspended(network)) - } - - override fun onNetworkResumed(network: Network) { - Log.d(TAG, "$network onNetworkResumed $network") - history.add(Resumed(network)) - } - - override fun onLosing(network: Network, maxMsToLive: Int) { - Log.d(TAG, "onLosing $network $maxMsToLive") - history.add(Losing(network, maxMsToLive)) - } - - override fun onLost(network: Network) { - Log.d(TAG, "onLost $network") - history.add(Lost(network)) - } - - override fun onUnavailable() { - Log.d(TAG, "onUnavailable") - history.add(Unavailable()) - } -} - -private const val DEFAULT_TIMEOUT = 30_000L // ms -private const val DEFAULT_NO_CALLBACK_TIMEOUT = 200L // ms -private val NOOP = Runnable {} - -/** - * See comments on the public constructor below for a description of the arguments. - */ -open class TestableNetworkCallback private constructor( - src: TestableNetworkCallback?, - val defaultTimeoutMs: Long = DEFAULT_TIMEOUT, - val defaultNoCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT, - val waiterFunc: Runnable = NOOP // "() -> Unit" would forbid calling with a void func from Java -) : RecorderCallback(src) { - /** - * Construct a testable network callback. - * @param timeoutMs the default timeout for expecting a callback. Default 30 seconds. This - * should be long in most cases, because the success case doesn't incur - * the wait. - * @param noCallbackTimeoutMs the timeout for expecting that no callback is received. Default - * 200ms. Because the success case does incur the timeout, this - * should be short in most cases, but not so short as to frequently - * time out before an incorrect callback is received. - * @param waiterFunc a function to use before asserting no callback. For some specific tests, - * it is useful to run test-specific code before asserting no callback to - * increase the likelihood that a spurious callback is correctly detected. - * As an example, a unit test using mock loopers may want to use this to - * make sure the loopers are drained before asserting no callback, since - * one of them may cause a callback to be called. @see ConnectivityServiceTest - * for such an example. - */ - @JvmOverloads - constructor( - timeoutMs: Long = DEFAULT_TIMEOUT, - noCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT, - waiterFunc: Runnable = NOOP - ) : this(null, timeoutMs, noCallbackTimeoutMs, waiterFunc) - - fun createLinkedCopy() = TestableNetworkCallback( - this, defaultTimeoutMs, defaultNoCallbackTimeoutMs, waiterFunc) - - // The last available network, or null if any network was lost since the last call to - // onAvailable. TODO : fix this by fixing the tests that rely on this behavior - val lastAvailableNetwork: Network? - get() = when (val it = history.lastOrNull { it is Available || it is Lost }) { - is Available -> it.network - else -> null - } - - /** - * Get the next callback or null if timeout. - * - * With no argument, this method waits out the default timeout. To wait forever, pass - * Long.MAX_VALUE. - */ - @JvmOverloads - fun poll(timeoutMs: Long = defaultTimeoutMs, predicate: (CallbackEntry) -> Boolean = { true }) = - history.poll(timeoutMs, predicate) - - /***** - * expect family of methods. - * These methods fetch the next callback and assert it matches the conditions : type, - * passed predicate. If no callback is received within the timeout, these methods fail. - */ - @JvmOverloads - fun <T : CallbackEntry> expect( - type: KClass<T>, - network: Network = ANY_NETWORK, - timeoutMs: Long = defaultTimeoutMs, - errorMsg: String? = null, - test: (T) -> Boolean = { true } - ) = expect<CallbackEntry>(network, timeoutMs, errorMsg) { - if (type.isInstance(it)) { - test(it as T) // Cast can't fail since type.isInstance(it) and type: KClass<T> - } else { - fail("Expected callback ${type.simpleName}, got $it") - } - } as T - - @JvmOverloads - fun <T : CallbackEntry> expect( - type: KClass<T>, - network: HasNetwork, - timeoutMs: Long = defaultTimeoutMs, - errorMsg: String? = null, - test: (T) -> Boolean = { true } - ) = expect(type, network.network, timeoutMs, errorMsg, test) - - // Java needs an explicit overload to let it omit arguments in the middle, so define these - // here. Note that @JvmOverloads give us the versions without the last arguments too, so - // there is no need to explicitly define versions without the test predicate. - // Without |network| - @JvmOverloads - fun <T : CallbackEntry> expect( - type: KClass<T>, - timeoutMs: Long, - errorMsg: String?, - test: (T) -> Boolean = { true } - ) = expect(type, ANY_NETWORK, timeoutMs, errorMsg, test) - - // Without |timeout|, in Network and HasNetwork versions - @JvmOverloads - fun <T : CallbackEntry> expect( - type: KClass<T>, - network: Network, - errorMsg: String?, - test: (T) -> Boolean = { true } - ) = expect(type, network, defaultTimeoutMs, errorMsg, test) - - @JvmOverloads - fun <T : CallbackEntry> expect( - type: KClass<T>, - network: HasNetwork, - errorMsg: String?, - test: (T) -> Boolean = { true } - ) = expect(type, network.network, defaultTimeoutMs, errorMsg, test) - - // Without |errorMsg|, in Network and HasNetwork versions - @JvmOverloads - fun <T : CallbackEntry> expect( - type: KClass<T>, - network: Network, - timeoutMs: Long, - test: (T) -> Boolean - ) = expect(type, network, timeoutMs, null, test) - - @JvmOverloads - fun <T : CallbackEntry> expect( - type: KClass<T>, - network: HasNetwork, - timeoutMs: Long, - test: (T) -> Boolean - ) = expect(type, network.network, timeoutMs, null, test) - - // Without |network| or |timeout| - @JvmOverloads - fun <T : CallbackEntry> expect( - type: KClass<T>, - errorMsg: String?, - test: (T) -> Boolean = { true } - ) = expect(type, ANY_NETWORK, defaultTimeoutMs, errorMsg, test) - - // Without |network| or |errorMsg| - @JvmOverloads - fun <T : CallbackEntry> expect( - type: KClass<T>, - timeoutMs: Long, - test: (T) -> Boolean = { true } - ) = expect(type, ANY_NETWORK, timeoutMs, null, test) - - // Without |timeout| or |errorMsg|, in Network and HasNetwork versions - @JvmOverloads - fun <T : CallbackEntry> expect( - type: KClass<T>, - network: Network, - test: (T) -> Boolean - ) = expect(type, network, defaultTimeoutMs, null, test) - - @JvmOverloads - fun <T : CallbackEntry> expect( - type: KClass<T>, - network: HasNetwork, - test: (T) -> Boolean - ) = expect(type, network.network, defaultTimeoutMs, null, test) - - // Without |network| or |timeout| or |errorMsg| - @JvmOverloads - fun <T : CallbackEntry> expect( - type: KClass<T>, - test: (T) -> Boolean - ) = expect(type, ANY_NETWORK, defaultTimeoutMs, null, test) - - // Kotlin reified versions. Don't call methods above, or the predicate would need to be noinline - inline fun <reified T : CallbackEntry> expect( - network: Network = ANY_NETWORK, - timeoutMs: Long = defaultTimeoutMs, - errorMsg: String? = null, - test: (T) -> Boolean = { true } - ) = (poll(timeoutMs) ?: fail("Did not receive ${T::class.simpleName} after ${timeoutMs}ms")) - .also { - if (it !is T) fail("Expected callback ${T::class.simpleName}, got $it") - if (ANY_NETWORK !== network && it.network != network) { - fail("Expected network $network for callback : $it") - } - if (!test(it)) { - fail("${errorMsg ?: "Callback doesn't match predicate"} : $it") - } - } as T - - inline fun <reified T : CallbackEntry> expect( - network: HasNetwork, - timeoutMs: Long = defaultTimeoutMs, - errorMsg: String? = null, - test: (T) -> Boolean = { true } - ) = expect(network.network, timeoutMs, errorMsg, test) - - /***** - * assertNoCallback family of methods. - * These methods make sure that no callback that matches the predicate was received. - * If no predicate is given, they make sure that no callback at all was received. - * These methods run the waiter func given in the constructor if any. - */ - @JvmOverloads - fun assertNoCallback( - timeoutMs: Long = defaultNoCallbackTimeoutMs, - valid: (CallbackEntry) -> Boolean = { true } - ) { - waiterFunc.run() - history.poll(timeoutMs) { valid(it) }?.let { fail("Expected no callback but got $it") } - } - - fun assertNoCallback(valid: (CallbackEntry) -> Boolean) = - assertNoCallback(defaultNoCallbackTimeoutMs, valid) - - /***** - * eventuallyExpect family of methods. - * These methods make sure a callback that matches the type/predicate is received eventually. - * Any callback of the wrong type, or doesn't match the optional predicate, is ignored. - * They fail if no callback matching the predicate is received within the timeout. - */ - inline fun <reified T : CallbackEntry> eventuallyExpect( - timeoutMs: Long = defaultTimeoutMs, - from: Int = mark, - crossinline predicate: (T) -> Boolean = { true } - ): T = history.poll(timeoutMs, from) { it is T && predicate(it) }.also { - assertNotNull(it, "Callback ${T::class} not received within ${timeoutMs}ms. " + - "Got ${history.backtrace()}") - } as T - - @JvmOverloads - fun <T : CallbackEntry> eventuallyExpect( - type: KClass<T>, - timeoutMs: Long = defaultTimeoutMs, - predicate: (cb: T) -> Boolean = { true } - ) = history.poll(timeoutMs) { type.java.isInstance(it) && predicate(it as T) }.also { - assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " + - "Got ${history.backtrace()}") - } as T - - fun <T : CallbackEntry> eventuallyExpect( - type: KClass<T>, - timeoutMs: Long = defaultTimeoutMs, - from: Int = mark, - predicate: (cb: T) -> Boolean = { true } - ) = history.poll(timeoutMs, from) { type.java.isInstance(it) && predicate(it as T) }.also { - assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " + - "Got ${history.backtrace()}") - } as T - - // Expects onAvailable and the callbacks that follow it. These are: - // - onSuspended, iff the network was suspended when the callbacks fire. - // - onCapabilitiesChanged. - // - onLinkPropertiesChanged. - // - onBlockedStatusChanged. - // - // @param network the network to expect the callbacks on. - // @param suspended whether to expect a SUSPENDED callback. - // @param validated the expected value of the VALIDATED capability in the - // onCapabilitiesChanged callback. - // @param tmt how long to wait for the callbacks. - @JvmOverloads - fun expectAvailableCallbacks( - net: Network, - suspended: Boolean = false, - validated: Boolean? = true, - blocked: Boolean = false, - tmt: Long = defaultTimeoutMs - ) { - expectAvailableCallbacksCommon(net, suspended, validated, tmt) - expect<BlockedStatus>(net, tmt) { it.blocked == blocked } - } - - fun expectAvailableCallbacks( - net: Network, - suspended: Boolean, - validated: Boolean, - blockedReason: Int, - tmt: Long - ) { - expectAvailableCallbacksCommon(net, suspended, validated, tmt) - expect<BlockedStatusInt>(net) { it.reason == blockedReason } - } - - private fun expectAvailableCallbacksCommon( - net: Network, - suspended: Boolean, - validated: Boolean?, - tmt: Long - ) { - expect<Available>(net, tmt) - if (suspended) { - expect<Suspended>(net, tmt) - } - expect<CapabilitiesChanged>(net, tmt) { - validated == null || validated == it.caps.hasCapability(NET_CAPABILITY_VALIDATED) - } - expect<LinkPropertiesChanged>(net, tmt) - } - - // Backward compatibility for existing Java code. Use named arguments instead and remove all - // these when there is no user left. - fun expectAvailableAndSuspendedCallbacks( - net: Network, - validated: Boolean, - tmt: Long = defaultTimeoutMs - ) = expectAvailableCallbacks(net, suspended = true, validated = validated, tmt = tmt) - - // Expects the available callbacks (where the onCapabilitiesChanged must contain the - // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the - // one we just sent. - // TODO: this is likely a bug. Fix it and remove this method. - fun expectAvailableDoubleValidatedCallbacks(net: Network, tmt: Long = defaultTimeoutMs) { - val mark = history.mark - expectAvailableCallbacks(net, tmt = tmt) - val firstCaps = history.poll(tmt, mark) { it is CapabilitiesChanged } - assertEquals(firstCaps, expect<CapabilitiesChanged>(net, tmt)) - } - - // Expects the available callbacks where the onCapabilitiesChanged must not have validated, - // then expects another onCapabilitiesChanged that has the validated bit set. This is used - // when a network connects and satisfies a callback, and then immediately validates. - fun expectAvailableThenValidatedCallbacks(net: Network, tmt: Long = defaultTimeoutMs) { - expectAvailableCallbacks(net, validated = false, tmt = tmt) - expectCaps(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) } - } - - fun expectAvailableThenValidatedCallbacks( - net: Network, - blockedReason: Int, - tmt: Long = defaultTimeoutMs - ) { - expectAvailableCallbacks(net, validated = false, suspended = false, - blockedReason = blockedReason, tmt = tmt) - expectCaps(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) } - } - - // Temporary Java compat measure : have MockNetworkAgent implement this so that all existing - // calls with networkAgent can be routed through here without moving MockNetworkAgent. - // TODO: clean this up, remove this method. - interface HasNetwork { - val network: Network - } - - fun expectAvailableCallbacks( - n: HasNetwork, - suspended: Boolean, - validated: Boolean, - blocked: Boolean, - timeoutMs: Long - ) = expectAvailableCallbacks(n.network, suspended, validated, blocked, timeoutMs) - - fun expectAvailableAndSuspendedCallbacks(n: HasNetwork, expectValidated: Boolean) { - expectAvailableAndSuspendedCallbacks(n.network, expectValidated) - } - - fun expectAvailableCallbacksValidated(n: HasNetwork) { - expectAvailableCallbacks(n.network) - } - - fun expectAvailableCallbacksValidatedAndBlocked(n: HasNetwork) { - expectAvailableCallbacks(n.network, blocked = true) - } - - fun expectAvailableCallbacksUnvalidated(n: HasNetwork) { - expectAvailableCallbacks(n.network, validated = false) - } - - fun expectAvailableCallbacksUnvalidatedAndBlocked(n: HasNetwork) { - expectAvailableCallbacks(n.network, validated = false, blocked = true) - } - - fun expectAvailableDoubleValidatedCallbacks(n: HasNetwork) { - expectAvailableDoubleValidatedCallbacks(n.network, defaultTimeoutMs) - } - - fun expectAvailableThenValidatedCallbacks(n: HasNetwork) { - expectAvailableThenValidatedCallbacks(n.network, defaultTimeoutMs) - } - - @JvmOverloads - fun expectCaps( - n: HasNetwork, - tmt: Long = defaultTimeoutMs, - valid: (NetworkCapabilities) -> Boolean = { true } - ) = expect<CapabilitiesChanged>(n.network, tmt) { valid(it.caps) }.caps - - @JvmOverloads - fun expectCaps( - n: Network, - tmt: Long = defaultTimeoutMs, - valid: (NetworkCapabilities) -> Boolean - ) = expect<CapabilitiesChanged>(n, tmt) { valid(it.caps) }.caps - - fun expectCaps( - n: HasNetwork, - valid: (NetworkCapabilities) -> Boolean - ) = expect<CapabilitiesChanged>(n.network) { valid(it.caps) }.caps - - fun expectCaps( - tmt: Long, - valid: (NetworkCapabilities) -> Boolean - ) = expect<CapabilitiesChanged>(ANY_NETWORK, tmt) { valid(it.caps) }.caps -} diff --git a/common/testutils/devicetests/com/android/testutils/TestableNetworkOfferCallback.kt b/common/testutils/devicetests/com/android/testutils/TestableNetworkOfferCallback.kt deleted file mode 100644 index 21bd60c9..00000000 --- a/common/testutils/devicetests/com/android/testutils/TestableNetworkOfferCallback.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.net.NetworkCapabilities -import android.net.NetworkProvider -import android.net.NetworkRequest -import android.util.Log -import com.android.net.module.util.ArrayTrackRecord -import kotlin.test.fail - -class TestableNetworkOfferCallback(val timeoutMs: Long, private val noCallbackTimeoutMs: Long) - : NetworkProvider.NetworkOfferCallback { - private val TAG = this::class.simpleName - val history = ArrayTrackRecord<CallbackEntry>().newReadHead() - - sealed class CallbackEntry { - data class OnNetworkNeeded(val request: NetworkRequest) : CallbackEntry() - data class OnNetworkUnneeded(val request: NetworkRequest) : CallbackEntry() - } - - /** - * Called by the system when a network for this offer is needed to satisfy some - * networking request. - */ - override fun onNetworkNeeded(request: NetworkRequest) { - Log.d(TAG, "onNetworkNeeded $request") - history.add(CallbackEntry.OnNetworkNeeded(request)) - } - - /** - * Called by the system when this offer is no longer valuable for this request. - */ - override fun onNetworkUnneeded(request: NetworkRequest) { - Log.d(TAG, "onNetworkUnneeded $request") - history.add(CallbackEntry.OnNetworkUnneeded(request)) - } - - inline fun <reified T : CallbackEntry> expectCallbackThat( - crossinline predicate: (T) -> Boolean - ) { - val event = history.poll(timeoutMs) - ?: fail("Did not receive callback after ${timeoutMs}ms") - if (event !is T || !predicate(event)) fail("Received unexpected callback $event") - } - - fun expectOnNetworkNeeded(capabilities: NetworkCapabilities) = - expectCallbackThat<CallbackEntry.OnNetworkNeeded> { - it.request.canBeSatisfiedBy(capabilities) - } - - fun expectOnNetworkUnneeded(capabilities: NetworkCapabilities) = - expectCallbackThat<CallbackEntry.OnNetworkUnneeded> { - it.request.canBeSatisfiedBy(capabilities) - } - - fun assertNoCallback() { - val cb = history.poll(noCallbackTimeoutMs) - if (null != cb) fail("Expected no callback but got $cb") - } -}
\ No newline at end of file diff --git a/common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt b/common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt deleted file mode 100644 index 4a7b3513..00000000 --- a/common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.net.netstats.provider.NetworkStatsProvider -import android.util.Log -import com.android.net.module.util.ArrayTrackRecord -import kotlin.test.assertEquals -import kotlin.test.assertTrue -import kotlin.test.fail - -private const val DEFAULT_TIMEOUT_MS = 200L -const val TOKEN_ANY = -1 - -open class TestableNetworkStatsProvider( - val defaultTimeoutMs: Long = DEFAULT_TIMEOUT_MS -) : NetworkStatsProvider() { - sealed class CallbackType { - data class OnRequestStatsUpdate(val token: Int) : CallbackType() - data class OnSetWarningAndLimit( - val iface: String, - val warningBytes: Long, - val limitBytes: Long - ) : CallbackType() - data class OnSetLimit(val iface: String, val limitBytes: Long) : CallbackType() { - // Add getter for backward compatibility since old tests do not recognize limitBytes. - val quotaBytes: Long - get() = limitBytes - } - data class OnSetAlert(val quotaBytes: Long) : CallbackType() - } - - private val TAG = this::class.simpleName - val history = ArrayTrackRecord<CallbackType>().newReadHead() - // See ReadHead#mark - val mark get() = history.mark - - override fun onRequestStatsUpdate(token: Int) { - Log.d(TAG, "onRequestStatsUpdate $token") - history.add(CallbackType.OnRequestStatsUpdate(token)) - } - - override fun onSetWarningAndLimit(iface: String, warningBytes: Long, limitBytes: Long) { - Log.d(TAG, "onSetWarningAndLimit $iface $warningBytes $limitBytes") - history.add(CallbackType.OnSetWarningAndLimit(iface, warningBytes, limitBytes)) - } - - override fun onSetLimit(iface: String, quotaBytes: Long) { - Log.d(TAG, "onSetLimit $iface $quotaBytes") - history.add(CallbackType.OnSetLimit(iface, quotaBytes)) - } - - override fun onSetAlert(quotaBytes: Long) { - Log.d(TAG, "onSetAlert $quotaBytes") - history.add(CallbackType.OnSetAlert(quotaBytes)) - } - - fun expectOnRequestStatsUpdate(token: Int, timeout: Long = defaultTimeoutMs): Int { - val event = history.poll(timeout) - assertTrue(event is CallbackType.OnRequestStatsUpdate) - if (token != TOKEN_ANY) { - assertEquals(token, event.token) - } - return event.token - } - - fun expectOnSetLimit(iface: String, quotaBytes: Long, timeout: Long = defaultTimeoutMs) { - assertEquals(CallbackType.OnSetLimit(iface, quotaBytes), history.poll(timeout)) - } - - fun expectOnSetAlert(quotaBytes: Long, timeout: Long = defaultTimeoutMs) { - assertEquals(CallbackType.OnSetAlert(quotaBytes), history.poll(timeout)) - } - - fun pollForNextCallback(timeout: Long = defaultTimeoutMs) = - history.poll(timeout) ?: fail("Did not receive callback after ${timeout}ms") - - inline fun <reified T : CallbackType> expectCallback( - timeout: Long = defaultTimeoutMs, - predicate: (T) -> Boolean = { true } - ): T { - return pollForNextCallback(timeout).also { assertTrue(it is T && predicate(it)) } as T - } - - // Expects a callback of the specified type matching the predicate within the timeout. - // Any callback that doesn't match the predicate will be skipped. Fails only if - // no matching callback is received within the timeout. - // TODO : factorize the code for this with the identical call in TestableNetworkCallback. - // There should be a common superclass doing this generically. - // TODO : have a better error message to have this fail. Right now the failure when no - // matching callback arrives comes from the casting to a non-nullable T. - // TODO : in fact, completely removing this method and have clients use - // history.poll(timeout, index, predicate) directly might be simpler. - inline fun <reified T : CallbackType> eventuallyExpect( - timeoutMs: Long = defaultTimeoutMs, - from: Int = mark, - crossinline predicate: (T) -> Boolean = { true } - ) = history.poll(timeoutMs, from) { it is T && predicate(it) } as T - - fun drainCallbacks() { - history.mark = history.size - } - - @JvmOverloads - fun assertNoCallback(timeout: Long = defaultTimeoutMs) { - val cb = history.poll(timeout) - cb?.let { fail("Expected no callback but got $cb") } - } -} diff --git a/common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderBinder.kt b/common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderBinder.kt deleted file mode 100644 index 643346b2..00000000 --- a/common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderBinder.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import com.android.net.module.util.ArrayTrackRecord -import kotlin.test.assertEquals -import kotlin.test.fail - -private const val DEFAULT_TIMEOUT_MS = 200L - -open class TestableNetworkStatsProviderBinder : NetworkStatsProviderStubCompat() { - sealed class CallbackType { - data class OnRequestStatsUpdate(val token: Int) : CallbackType() - data class OnSetAlert(val quotaBytes: Long) : CallbackType() - data class OnSetWarningAndLimit( - val iface: String, - val warningBytes: Long, - val limitBytes: Long - ) : CallbackType() - } - - private val history = ArrayTrackRecord<CallbackType>().ReadHead() - - override fun onRequestStatsUpdate(token: Int) { - history.add(CallbackType.OnRequestStatsUpdate(token)) - } - - override fun onSetAlert(quotaBytes: Long) { - history.add(CallbackType.OnSetAlert(quotaBytes)) - } - - override fun onSetWarningAndLimit(iface: String, warningBytes: Long, limitBytes: Long) { - history.add(CallbackType.OnSetWarningAndLimit(iface, warningBytes, limitBytes)) - } - - fun expectOnRequestStatsUpdate(token: Int) { - assertEquals(CallbackType.OnRequestStatsUpdate(token), history.poll(DEFAULT_TIMEOUT_MS)) - } - - fun expectOnSetWarningAndLimit(iface: String, warningBytes: Long, limitBytes: Long) { - assertEquals(CallbackType.OnSetWarningAndLimit(iface, warningBytes, limitBytes), - history.poll(DEFAULT_TIMEOUT_MS)) - } - - fun expectOnSetAlert(quotaBytes: Long) { - assertEquals(CallbackType.OnSetAlert(quotaBytes), history.poll(DEFAULT_TIMEOUT_MS)) - } - - @JvmOverloads - fun assertNoCallback(timeout: Long = DEFAULT_TIMEOUT_MS) { - val cb = history.poll(timeout) - cb?.let { fail("Expected no callback but got $cb") } - } -} diff --git a/common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt b/common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt deleted file mode 100644 index 5547c90a..00000000 --- a/common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils - -import android.net.NetworkStats -import com.android.net.module.util.ArrayTrackRecord -import kotlin.test.assertEquals -import kotlin.test.assertTrue -import kotlin.test.fail - -private const val DEFAULT_TIMEOUT_MS = 3000L - -open class TestableNetworkStatsProviderCbBinder : NetworkStatsProviderCbStubCompat() { - sealed class CallbackType { - data class NotifyStatsUpdated( - val token: Int, - val ifaceStats: NetworkStats, - val uidStats: NetworkStats - ) : CallbackType() - object NotifyWarningReached : CallbackType() - object NotifyLimitReached : CallbackType() - object NotifyWarningOrLimitReached : CallbackType() - object NotifyAlertReached : CallbackType() - object Unregister : CallbackType() - } - - private val history = ArrayTrackRecord<CallbackType>().ReadHead() - - override fun notifyStatsUpdated(token: Int, ifaceStats: NetworkStats, uidStats: NetworkStats) { - history.add(CallbackType.NotifyStatsUpdated(token, ifaceStats, uidStats)) - } - - override fun notifyWarningReached() { - history.add(CallbackType.NotifyWarningReached) - } - - override fun notifyLimitReached() { - history.add(CallbackType.NotifyLimitReached) - } - - override fun notifyWarningOrLimitReached() { - // Older callback is split into notifyLimitReached and notifyWarningReached in T. - history.add(CallbackType.NotifyWarningOrLimitReached) - } - - override fun notifyAlertReached() { - history.add(CallbackType.NotifyAlertReached) - } - - override fun unregister() { - history.add(CallbackType.Unregister) - } - - fun expectNotifyStatsUpdated() { - val event = history.poll(DEFAULT_TIMEOUT_MS) - assertTrue(event is CallbackType.NotifyStatsUpdated) - } - - fun expectNotifyStatsUpdated(ifaceStats: NetworkStats, uidStats: NetworkStats) { - val event = history.poll(DEFAULT_TIMEOUT_MS)!! - if (event !is CallbackType.NotifyStatsUpdated) { - throw Exception("Expected NotifyStatsUpdated callback, but got ${event::class}") - } - // TODO: verify token. - assertNetworkStatsEquals(ifaceStats, event.ifaceStats) - assertNetworkStatsEquals(uidStats, event.uidStats) - } - - fun expectNotifyWarningReached() = - assertEquals(CallbackType.NotifyWarningReached, history.poll(DEFAULT_TIMEOUT_MS)) - - fun expectNotifyLimitReached() = - assertEquals(CallbackType.NotifyLimitReached, history.poll(DEFAULT_TIMEOUT_MS)) - - fun expectNotifyWarningOrLimitReached() = - assertEquals(CallbackType.NotifyWarningOrLimitReached, history.poll(DEFAULT_TIMEOUT_MS)) - - fun expectNotifyAlertReached() = - assertEquals(CallbackType.NotifyAlertReached, history.poll(DEFAULT_TIMEOUT_MS)) - - // Assert there is no callback in current queue. - fun assertNoCallback() { - val cb = history.poll(0) - cb?.let { fail("Expected no callback but got $cb") } - } -} diff --git a/common/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java b/common/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java deleted file mode 100644 index 48b57d70..00000000 --- a/common/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java +++ /dev/null @@ -1,568 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils.async; - -import android.os.ParcelFileDescriptor; -import android.system.StructPollfd; -import android.util.Log; - -import com.android.net.module.util.async.CircularByteBuffer; -import com.android.net.module.util.async.OsAccess; - -import java.io.FileDescriptor; -import java.io.InterruptedIOException; -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.concurrent.TimeUnit; - -public class FakeOsAccess extends OsAccess { - public static final boolean ENABLE_FINE_DEBUG = true; - - public static final int DEFAULT_FILE_DATA_QUEUE_SIZE = 8 * 1024; - - private enum FileType { PAIR, PIPE } - - // Common poll() constants: - private static final short POLLIN = 0x0001; - private static final short POLLOUT = 0x0004; - private static final short POLLERR = 0x0008; - private static final short POLLHUP = 0x0010; - - private static final Constructor<FileDescriptor> FD_CONSTRUCTOR; - private static final Field FD_FIELD_DESCRIPTOR; - private static final Field PFD_FIELD_DESCRIPTOR; - private static final Field PFD_FIELD_GUARD; - private static final Method CLOSE_GUARD_METHOD_CLOSE; - - private final int mReadQueueSize = DEFAULT_FILE_DATA_QUEUE_SIZE; - private final int mWriteQueueSize = DEFAULT_FILE_DATA_QUEUE_SIZE; - private final HashMap<Integer, File> mFiles = new HashMap<>(); - private final byte[] mTmpBuffer = new byte[1024]; - private final long mStartTime; - private final String mLogTag; - private int mFileNumberGen = 3; - private boolean mHasRateLimitedData; - - public FakeOsAccess(String logTag) { - mLogTag = logTag; - mStartTime = monotonicTimeMillis(); - } - - @Override - public long monotonicTimeMillis() { - return System.nanoTime() / 1000000; - } - - @Override - public FileDescriptor getInnerFileDescriptor(ParcelFileDescriptor fd) { - try { - return (FileDescriptor) PFD_FIELD_DESCRIPTOR.get(fd); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void close(ParcelFileDescriptor fd) { - if (fd != null) { - close(getInnerFileDescriptor(fd)); - - try { - // Reduce CloseGuard warnings. - Object guard = PFD_FIELD_GUARD.get(fd); - CLOSE_GUARD_METHOD_CLOSE.invoke(guard); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - public synchronized void close(FileDescriptor fd) { - if (fd != null) { - File file = getFileOrNull(fd); - if (file != null) { - file.decreaseRefCount(); - mFiles.remove(getFileDescriptorNumber(fd)); - setFileDescriptorNumber(fd, -1); - notifyAll(); - } - } - } - - private File getFile(String func, FileDescriptor fd) throws IOException { - File file = getFileOrNull(fd); - if (file == null) { - throw newIOException(func, "Unknown file descriptor: " + getFileDebugName(fd)); - } - return file; - } - - private File getFileOrNull(FileDescriptor fd) { - return mFiles.get(getFileDescriptorNumber(fd)); - } - - @Override - public String getFileDebugName(ParcelFileDescriptor fd) { - return (fd != null ? getFileDebugName(getInnerFileDescriptor(fd)) : "null"); - } - - public String getFileDebugName(FileDescriptor fd) { - if (fd == null) { - return "null"; - } - - final int fdNumber = getFileDescriptorNumber(fd); - File file = mFiles.get(fdNumber); - - StringBuilder sb = new StringBuilder(); - if (file != null) { - if (file.name != null) { - sb.append(file.name); - sb.append("/"); - } - sb.append(file.type); - sb.append("/"); - } else { - sb.append("BADFD/"); - } - sb.append(fdNumber); - return sb.toString(); - } - - public synchronized void setFileName(FileDescriptor fd, String name) { - File file = getFileOrNull(fd); - if (file != null) { - file.name = name; - } - } - - @Override - public synchronized void setNonBlocking(FileDescriptor fd) throws IOException { - File file = getFile("fcntl", fd); - file.isBlocking = false; - } - - @Override - public synchronized int read(FileDescriptor fd, byte[] buffer, int pos, int len) - throws IOException { - checkBoundaries("read", buffer, pos, len); - - File file = getFile("read", fd); - if (file.readQueue == null) { - throw newIOException("read", "File not readable"); - } - file.checkNonBlocking("read"); - - if (len == 0) { - return 0; - } - - final int availSize = file.readQueue.size(); - if (availSize == 0) { - if (file.isEndOfStream) { - // Java convention uses -1 to indicate end of stream. - return -1; - } - return 0; // EAGAIN - } - - final int readCount = Math.min(len, availSize); - file.readQueue.readBytes(buffer, pos, readCount); - maybeTransferData(file); - return readCount; - } - - @Override - public synchronized int write(FileDescriptor fd, byte[] buffer, int pos, int len) - throws IOException { - checkBoundaries("write", buffer, pos, len); - - File file = getFile("write", fd); - if (file.writeQueue == null) { - throw newIOException("read", "File not writable"); - } - if (file.type == FileType.PIPE && file.sink.openCount == 0) { - throw newIOException("write", "The other end of pipe is closed"); - } - file.checkNonBlocking("write"); - - if (len == 0) { - return 0; - } - - final int originalFreeSize = file.writeQueue.freeSize(); - if (originalFreeSize == 0) { - return 0; // EAGAIN - } - - final int writeCount = Math.min(len, originalFreeSize); - file.writeQueue.writeBytes(buffer, pos, writeCount); - maybeTransferData(file); - - if (file.writeQueue.freeSize() < originalFreeSize) { - final int additionalQueuedCount = originalFreeSize - file.writeQueue.freeSize(); - Log.i(mLogTag, logStr("Delaying transfer of " + additionalQueuedCount - + " bytes, queued=" + file.writeQueue.size() + ", type=" + file.type - + ", src_red=" + file.outboundLimiter + ", dst_red=" + file.sink.inboundLimiter)); - } - - return writeCount; - } - - private void maybeTransferData(File file) { - boolean hasChanges = copyFileBuffers(file, file.sink); - hasChanges = copyFileBuffers(file.source, file) || hasChanges; - - if (hasChanges) { - // TODO(b/245971639): Avoid notifying if no-one is polling. - notifyAll(); - } - } - - private boolean copyFileBuffers(File src, File dst) { - if (src.writeQueue == null || dst.readQueue == null) { - return false; - } - - final int originalCopyCount = Math.min(mTmpBuffer.length, - Math.min(src.writeQueue.size(), dst.readQueue.freeSize())); - - final int allowedCopyCount = RateLimiter.limit( - src.outboundLimiter, dst.inboundLimiter, originalCopyCount); - - if (allowedCopyCount < originalCopyCount) { - if (ENABLE_FINE_DEBUG) { - Log.i(mLogTag, logStr("Delaying transfer of " - + (originalCopyCount - allowedCopyCount) + " bytes, original=" - + originalCopyCount + ", allowed=" + allowedCopyCount - + ", type=" + src.type)); - } - if (originalCopyCount > 0) { - mHasRateLimitedData = true; - } - if (allowedCopyCount == 0) { - return false; - } - } - - boolean hasChanges = false; - if (allowedCopyCount > 0) { - if (dst.readQueue.size() == 0 || src.writeQueue.freeSize() == 0) { - hasChanges = true; // Read queue had no data, or write queue was full. - } - src.writeQueue.readBytes(mTmpBuffer, 0, allowedCopyCount); - dst.readQueue.writeBytes(mTmpBuffer, 0, allowedCopyCount); - } - - if (!dst.isEndOfStream && src.openCount == 0 - && src.writeQueue.size() == 0 && dst.readQueue.size() == 0) { - dst.isEndOfStream = true; - hasChanges = true; - } - - return hasChanges; - } - - public void clearInboundRateLimit(FileDescriptor fd) { - setInboundRateLimit(fd, Integer.MAX_VALUE); - } - - public void clearOutboundRateLimit(FileDescriptor fd) { - setOutboundRateLimit(fd, Integer.MAX_VALUE); - } - - public synchronized void setInboundRateLimit(FileDescriptor fd, int bytesPerSecond) { - File file = getFileOrNull(fd); - if (file != null) { - file.inboundLimiter.setBytesPerSecond(bytesPerSecond); - maybeTransferData(file); - } - } - - public synchronized void setOutboundRateLimit(FileDescriptor fd, int bytesPerSecond) { - File file = getFileOrNull(fd); - if (file != null) { - file.outboundLimiter.setBytesPerSecond(bytesPerSecond); - maybeTransferData(file); - } - } - - public synchronized ParcelFileDescriptor[] socketpair() throws IOException { - int fdNumber1 = getNextFd("socketpair"); - int fdNumber2 = getNextFd("socketpair"); - - File file1 = new File(FileType.PAIR, mReadQueueSize, mWriteQueueSize); - File file2 = new File(FileType.PAIR, mReadQueueSize, mWriteQueueSize); - - return registerFilePair(fdNumber1, file1, fdNumber2, file2); - } - - @Override - public synchronized ParcelFileDescriptor[] pipe() throws IOException { - int fdNumber1 = getNextFd("pipe"); - int fdNumber2 = getNextFd("pipe"); - - File file1 = new File(FileType.PIPE, mReadQueueSize, 0); - File file2 = new File(FileType.PIPE, 0, mWriteQueueSize); - - return registerFilePair(fdNumber1, file1, fdNumber2, file2); - } - - private ParcelFileDescriptor[] registerFilePair( - int fdNumber1, File file1, int fdNumber2, File file2) { - file1.sink = file2; - file1.source = file2; - file2.sink = file1; - file2.source = file1; - - mFiles.put(fdNumber1, file1); - mFiles.put(fdNumber2, file2); - return new ParcelFileDescriptor[] { - newParcelFileDescriptor(fdNumber1), newParcelFileDescriptor(fdNumber2)}; - } - - @Override - public short getPollInMask() { - return POLLIN; - } - - @Override - public short getPollOutMask() { - return POLLOUT; - } - - @Override - public synchronized int poll(StructPollfd[] fds, int timeoutMs) throws IOException { - if (timeoutMs < 0) { - timeoutMs = (int) TimeUnit.HOURS.toMillis(1); // Make "infinite" equal to 1 hour. - } - - if (fds == null || fds.length > 1000) { - throw newIOException("poll", "Invalid fds param"); - } - for (StructPollfd pollFd : fds) { - getFile("poll", pollFd.fd); - } - - int waitCallCount = 0; - final long deadline = monotonicTimeMillis() + timeoutMs; - while (true) { - if (mHasRateLimitedData) { - mHasRateLimitedData = false; - for (File file : mFiles.values()) { - if (file.inboundLimiter.getLastRequestReduction() != 0) { - copyFileBuffers(file.source, file); - } - if (file.outboundLimiter.getLastRequestReduction() != 0) { - copyFileBuffers(file, file.sink); - } - } - } - - final int readyCount = calculateReadyCount(fds); - if (readyCount > 0) { - if (ENABLE_FINE_DEBUG) { - Log.v(mLogTag, logStr("Poll returns " + readyCount - + " after " + waitCallCount + " wait calls")); - } - return readyCount; - } - - long remainingTimeoutMs = deadline - monotonicTimeMillis(); - if (remainingTimeoutMs <= 0) { - if (ENABLE_FINE_DEBUG) { - Log.v(mLogTag, logStr("Poll timeout " + timeoutMs - + "ms after " + waitCallCount + " wait calls")); - } - return 0; - } - - if (mHasRateLimitedData) { - remainingTimeoutMs = Math.min(RateLimiter.BUCKET_DURATION_MS, remainingTimeoutMs); - } - - try { - wait(remainingTimeoutMs); - } catch (InterruptedException e) { - // Ignore and retry - } - waitCallCount++; - } - } - - private int calculateReadyCount(StructPollfd[] fds) { - int fdCount = 0; - for (StructPollfd pollFd : fds) { - pollFd.revents = 0; - - File file = getFileOrNull(pollFd.fd); - if (file == null) { - Log.w(mLogTag, logStr("Ignoring FD concurrently closed by a buggy app: " - + getFileDebugName(pollFd.fd))); - continue; - } - - if (ENABLE_FINE_DEBUG) { - Log.v(mLogTag, logStr("calculateReadyCount fd=" + getFileDebugName(pollFd.fd) - + ", events=" + pollFd.events + ", eof=" + file.isEndOfStream - + ", r=" + (file.readQueue != null ? file.readQueue.size() : -1) - + ", w=" + (file.writeQueue != null ? file.writeQueue.freeSize() : -1))); - } - - if ((pollFd.events & POLLIN) != 0) { - if (file.readQueue != null && file.readQueue.size() != 0) { - pollFd.revents |= POLLIN; - } - if (file.isEndOfStream) { - pollFd.revents |= POLLHUP; - } - } - - if ((pollFd.events & POLLOUT) != 0) { - if (file.type == FileType.PIPE && file.sink.openCount == 0) { - pollFd.revents |= POLLERR; - } - if (file.writeQueue != null && file.writeQueue.freeSize() != 0) { - pollFd.revents |= POLLOUT; - } - } - - if (pollFd.revents != 0) { - fdCount++; - } - } - return fdCount; - } - - private int getNextFd(String func) throws IOException { - if (mFileNumberGen > 100000) { - throw newIOException(func, "Too many files open"); - } - - return mFileNumberGen++; - } - - private static IOException newIOException(String func, String message) { - return new IOException(message + ", func=" + func); - } - - public static void checkBoundaries(String func, byte[] buffer, int pos, int len) - throws IOException { - if (((buffer.length | pos | len) < 0 || pos > buffer.length - len)) { - throw newIOException(func, "Invalid array bounds"); - } - } - - private ParcelFileDescriptor newParcelFileDescriptor(int fdNumber) { - try { - return new ParcelFileDescriptor(newFileDescriptor(fdNumber)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private FileDescriptor newFileDescriptor(int fdNumber) { - try { - return FD_CONSTRUCTOR.newInstance(Integer.valueOf(fdNumber)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public int getFileDescriptorNumber(FileDescriptor fd) { - try { - return (Integer) FD_FIELD_DESCRIPTOR.get(fd); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private void setFileDescriptorNumber(FileDescriptor fd, int fdNumber) { - try { - FD_FIELD_DESCRIPTOR.set(fd, Integer.valueOf(fdNumber)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private String logStr(String message) { - return "[FakeOs " + (monotonicTimeMillis() - mStartTime) + "] " + message; - } - - private class File { - final FileType type; - final CircularByteBuffer readQueue; - final CircularByteBuffer writeQueue; - final RateLimiter inboundLimiter = new RateLimiter(FakeOsAccess.this, Integer.MAX_VALUE); - final RateLimiter outboundLimiter = new RateLimiter(FakeOsAccess.this, Integer.MAX_VALUE); - String name; - int openCount = 1; - boolean isBlocking = true; - File sink; - File source; - boolean isEndOfStream; - - File(FileType type, int readQueueSize, int writeQueueSize) { - this.type = type; - readQueue = (readQueueSize > 0 ? new CircularByteBuffer(readQueueSize) : null); - writeQueue = (writeQueueSize > 0 ? new CircularByteBuffer(writeQueueSize) : null); - } - - void decreaseRefCount() { - if (openCount <= 0) { - throw new IllegalStateException(); - } - openCount--; - } - - void checkNonBlocking(String func) throws IOException { - if (isBlocking) { - throw newIOException(func, "File in blocking mode"); - } - } - } - - static { - try { - FD_CONSTRUCTOR = FileDescriptor.class.getDeclaredConstructor(int.class); - FD_CONSTRUCTOR.setAccessible(true); - - Field descriptorIntField; - try { - descriptorIntField = FileDescriptor.class.getDeclaredField("descriptor"); - } catch (NoSuchFieldException e) { - descriptorIntField = FileDescriptor.class.getDeclaredField("fd"); - } - FD_FIELD_DESCRIPTOR = descriptorIntField; - FD_FIELD_DESCRIPTOR.setAccessible(true); - - PFD_FIELD_DESCRIPTOR = ParcelFileDescriptor.class.getDeclaredField("mFd"); - PFD_FIELD_DESCRIPTOR.setAccessible(true); - - PFD_FIELD_GUARD = ParcelFileDescriptor.class.getDeclaredField("mGuard"); - PFD_FIELD_GUARD.setAccessible(true); - - CLOSE_GUARD_METHOD_CLOSE = Class.forName("dalvik.system.CloseGuard") - .getDeclaredMethod("close"); - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/common/testutils/devicetests/com/android/testutils/async/RateLimiter.java b/common/testutils/devicetests/com/android/testutils/async/RateLimiter.java deleted file mode 100644 index d5cca0a4..00000000 --- a/common/testutils/devicetests/com/android/testutils/async/RateLimiter.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils.async; - -import com.android.net.module.util.async.OsAccess; - -import java.util.Arrays; - -/** - * Limits the number of bytes processed to the given maximum of bytes per second. - * - * The limiter tracks the total for the past second, along with sums for each 10ms - * in the past second, allowing the total to be adjusted as the time passes. - */ -public final class RateLimiter { - private static final int PERIOD_DURATION_MS = 1000; - private static final int BUCKET_COUNT = 100; - - public static final int BUCKET_DURATION_MS = PERIOD_DURATION_MS / BUCKET_COUNT; - - private final OsAccess mOsAccess; - private final int[] mStatBuckets = new int[BUCKET_COUNT]; - private int mMaxPerPeriodBytes; - private int mMaxPerBucketBytes; - private int mRecordedPeriodBytes; - private long mLastLimitTimestamp; - private int mLastRequestReduction; - - public RateLimiter(OsAccess osAccess, int bytesPerSecond) { - mOsAccess = osAccess; - setBytesPerSecond(bytesPerSecond); - clear(); - } - - public int getBytesPerSecond() { - return mMaxPerPeriodBytes; - } - - public void setBytesPerSecond(int bytesPerSecond) { - mMaxPerPeriodBytes = bytesPerSecond; - mMaxPerBucketBytes = Math.max(1, (mMaxPerPeriodBytes / BUCKET_COUNT) * 2); - } - - public void clear() { - mLastLimitTimestamp = mOsAccess.monotonicTimeMillis(); - mRecordedPeriodBytes = 0; - Arrays.fill(mStatBuckets, 0); - } - - public static int limit(RateLimiter limiter1, RateLimiter limiter2, int requestedBytes) { - final long now = limiter1.mOsAccess.monotonicTimeMillis(); - final int allowedCount = Math.min(limiter1.calculateLimit(now, requestedBytes), - limiter2.calculateLimit(now, requestedBytes)); - limiter1.recordBytes(now, requestedBytes, allowedCount); - limiter2.recordBytes(now, requestedBytes, allowedCount); - return allowedCount; - } - - public int limit(int requestedBytes) { - final long now = mOsAccess.monotonicTimeMillis(); - final int allowedCount = calculateLimit(now, requestedBytes); - recordBytes(now, requestedBytes, allowedCount); - return allowedCount; - } - - public int getLastRequestReduction() { - return mLastRequestReduction; - } - - public boolean acceptAllOrNone(int requestedBytes) { - final long now = mOsAccess.monotonicTimeMillis(); - final int allowedCount = calculateLimit(now, requestedBytes); - if (allowedCount < requestedBytes) { - return false; - } - recordBytes(now, requestedBytes, allowedCount); - return true; - } - - private int calculateLimit(long now, int requestedBytes) { - // First remove all stale bucket data and adjust the total. - final long currentBucketAbsIdx = now / BUCKET_DURATION_MS; - final long staleCutoffIdx = currentBucketAbsIdx - BUCKET_COUNT; - for (long i = mLastLimitTimestamp / BUCKET_DURATION_MS; i < staleCutoffIdx; i++) { - final int idx = (int) (i % BUCKET_COUNT); - mRecordedPeriodBytes -= mStatBuckets[idx]; - mStatBuckets[idx] = 0; - } - - final int bucketIdx = (int) (currentBucketAbsIdx % BUCKET_COUNT); - final int maxAllowed = Math.min(mMaxPerPeriodBytes - mRecordedPeriodBytes, - Math.min(mMaxPerBucketBytes - mStatBuckets[bucketIdx], requestedBytes)); - return Math.max(0, maxAllowed); - } - - private void recordBytes(long now, int requestedBytes, int actualBytes) { - mStatBuckets[(int) ((now / BUCKET_DURATION_MS) % BUCKET_COUNT)] += actualBytes; - mRecordedPeriodBytes += actualBytes; - mLastRequestReduction = requestedBytes - actualBytes; - mLastLimitTimestamp = now; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("{max="); - sb.append(mMaxPerPeriodBytes); - sb.append(",max_bucket="); - sb.append(mMaxPerBucketBytes); - sb.append(",total="); - sb.append(mRecordedPeriodBytes); - sb.append(",last_red="); - sb.append(mLastRequestReduction); - sb.append('}'); - return sb.toString(); - } -} diff --git a/common/testutils/devicetests/com/android/testutils/async/ReadableDataAnswer.java b/common/testutils/devicetests/com/android/testutils/async/ReadableDataAnswer.java deleted file mode 100644 index 4bf55270..00000000 --- a/common/testutils/devicetests/com/android/testutils/async/ReadableDataAnswer.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils.async; - -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.util.ArrayList; - -public class ReadableDataAnswer implements Answer { - private final ArrayList<byte[]> mBuffers = new ArrayList<>(); - private int mBufferPos; - - public ReadableDataAnswer(byte[] ... buffers) { - for (byte[] buffer : buffers) { - addBuffer(buffer); - } - } - - public void addBuffer(byte[] buffer) { - if (buffer.length != 0) { - mBuffers.add(buffer); - } - } - - public int getRemainingSize() { - int totalSize = 0; - for (byte[] buffer : mBuffers) { - totalSize += buffer.length; - } - return totalSize - mBufferPos; - } - - private void cleanupBuffers() { - if (!mBuffers.isEmpty() && mBufferPos == mBuffers.get(0).length) { - mBuffers.remove(0); - mBufferPos = 0; - } - } - - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - cleanupBuffers(); - - if (mBuffers.isEmpty()) { - return Integer.valueOf(0); - } - - byte[] src = mBuffers.get(0); - - byte[] dst = invocation.<byte[]>getArgument(0); - int dstPos = invocation.<Integer>getArgument(1); - int dstLen = invocation.<Integer>getArgument(2); - - int copyLen = Math.min(dstLen, src.length - mBufferPos); - System.arraycopy(src, mBufferPos, dst, dstPos, copyLen); - mBufferPos += copyLen; - - cleanupBuffers(); - return Integer.valueOf(copyLen); - } -} diff --git a/common/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk30.kt b/common/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk30.kt deleted file mode 100644 index 843c41e6..00000000 --- a/common/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk30.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils.filters - -/** - * Only run this test in the CtsNetTestCasesMaxTargetSdk30 suite. - */ -annotation class CtsNetTestCasesMaxTargetSdk30(val reason: String) diff --git a/common/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk31.kt b/common/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk31.kt deleted file mode 100644 index be0103d6..00000000 --- a/common/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk31.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils.filters - -/** - * Only run this test in the CtsNetTestCasesMaxTargetSdk31 suite. - */ -annotation class CtsNetTestCasesMaxTargetSdk31(val reason: String) diff --git a/common/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk33.kt b/common/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk33.kt deleted file mode 100644 index 5af890ff..00000000 --- a/common/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk33.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.testutils.filters - -/** - * Only run this test in the CtsNetTestCasesMaxTargetSdk33 suite. - */ -annotation class CtsNetTestCasesMaxTargetSdk33(val reason: String) |