Skip ab/6749736 in stage.
Merged-In: Id9caa92738407f39ad90b0a1bc948e3532c5106f
Change-Id: I801485328c077f2505688a06f9b173197c8db6e0
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 4b15b16..e5cc8b1 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -98,6 +98,7 @@
],
static_libs: [
"androidx.test.ext.junit",
+ "libnanohttpd",
"net-tests-utils-host-device-common",
"net-utils-device-common",
],
@@ -125,6 +126,7 @@
"//frameworks/base/packages/Tethering",
"//frameworks/opt/net/ike",
"//frameworks/opt/telephony",
+ "//frameworks/base/wifi:__subpackages__",
"//packages/modules/NetworkStack:__subpackages__",
"//packages/modules/CaptivePortalLogin",
"//frameworks/libs/net/common/tests:__subpackages__",
diff --git a/staticlibs/devicetests/com/android/testutils/ArpResponder.kt b/staticlibs/devicetests/com/android/testutils/ArpResponder.kt
new file mode 100644
index 0000000..86631c3
--- /dev/null
+++ b/staticlibs/devicetests/com/android/testutils/ArpResponder.kt
@@ -0,0 +1,60 @@
+/*
+ * 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 java.net.Inet4Address
+import java.net.InetAddress
+import java.nio.ByteBuffer
+
+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/staticlibs/devicetests/com/android/testutils/HandlerUtils.kt b/staticlibs/devicetests/com/android/testutils/HandlerUtils.kt
index 336e12c..861f45e 100644
--- a/staticlibs/devicetests/com/android/testutils/HandlerUtils.kt
+++ b/staticlibs/devicetests/com/android/testutils/HandlerUtils.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:JvmName("HandlerUtils")
+
package com.android.testutils
import android.os.ConditionVariable
diff --git a/staticlibs/devicetests/com/android/testutils/PacketResponder.kt b/staticlibs/devicetests/com/android/testutils/PacketResponder.kt
new file mode 100644
index 0000000..daf29e4
--- /dev/null
+++ b/staticlibs/devicetests/com/android/testutils/PacketResponder.kt
@@ -0,0 +1,74 @@
+/*
+ * 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()
+ }
+
+ 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/staticlibs/devicetests/com/android/testutils/ParcelUtils.kt b/staticlibs/devicetests/com/android/testutils/ParcelUtils.kt
index 5784f7c..14ed8e9 100644
--- a/staticlibs/devicetests/com/android/testutils/ParcelUtils.kt
+++ b/staticlibs/devicetests/com/android/testutils/ParcelUtils.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:JvmName("ParcelUtils")
+
package com.android.testutils
import android.os.Parcel
diff --git a/staticlibs/devicetests/com/android/testutils/TapPacketReader.java b/staticlibs/devicetests/com/android/testutils/TapPacketReader.java
index c36f144..001b709 100644
--- a/staticlibs/devicetests/com/android/testutils/TapPacketReader.java
+++ b/staticlibs/devicetests/com/android/testutils/TapPacketReader.java
@@ -50,6 +50,17 @@
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;
@@ -64,10 +75,30 @@
}
/**
+ * @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[] popPacket(long timeoutMs) {
+ public byte[] poll(long timeoutMs) {
return mReadHead.getValue().poll(timeoutMs, packet -> true);
}
@@ -75,7 +106,7 @@
* Get the next packet that was received on the interface and matches the specified filter.
*/
@Nullable
- public byte[] popPacket(long timeoutMs, @NonNull Predicate<byte[]> filter) {
+ public byte[] poll(long timeoutMs, @NonNull Predicate<byte[]> filter) {
return mReadHead.getValue().poll(timeoutMs, filter::test);
}
diff --git a/staticlibs/devicetests/com/android/testutils/TapPacketReaderRule.kt b/staticlibs/devicetests/com/android/testutils/TapPacketReaderRule.kt
new file mode 100644
index 0000000..701666c
--- /dev/null
+++ b/staticlibs/devicetests/com/android/testutils/TapPacketReaderRule.kt
@@ -0,0 +1,154 @@
+/*
+ * 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/staticlibs/devicetests/com/android/testutils/TestHttpServer.kt b/staticlibs/devicetests/com/android/testutils/TestHttpServer.kt
new file mode 100644
index 0000000..7aae8e3
--- /dev/null
+++ b/staticlibs/devicetests/com/android/testutils/TestHttpServer.kt
@@ -0,0 +1,86 @@
+/*
+ * 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
+
+/**
+ * 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,
+ locationHeader: String? = null,
+ content: String = ""
+ ) {
+ addResponse(Request(uri.path
+ ?: "", Method.GET, uri.query ?: ""),
+ statusCode, locationHeader, content)
+ }
+
+ /**
+ * Add a response for the given request.
+ */
+ fun addResponse(
+ request: Request,
+ statusCode: Response.IStatus,
+ locationHeader: String? = null,
+ content: String = ""
+ ) {
+ val response = newFixedLengthResponse(statusCode, "text/plain", content)
+ locationHeader?.let { response.addHeader("Location", it) }
+ responses[request] = response
+ }
+
+ override fun serve(session: IHTTPSession): Response {
+ val request = Request(session.uri
+ ?: "", session.method, session.queryParameterString ?: "")
+ requestsRecord.add(request)
+ // Default response is a 404
+ return responses[request] ?: super.serve(session)
+ }
+}
\ No newline at end of file
diff --git a/staticlibs/devicetests/com/android/testutils/TestPermissionUtil.kt b/staticlibs/devicetests/com/android/testutils/TestPermissionUtil.kt
new file mode 100644
index 0000000..f557f18
--- /dev/null
+++ b/staticlibs/devicetests/com/android/testutils/TestPermissionUtil.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.testutils.ExceptionUtils.ThrowingRunnable
+import com.android.testutils.ExceptionUtils.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
+ 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/staticlibs/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/devicetests/com/android/testutils/TestableNetworkCallback.kt
index 959a837..7cd5419 100644
--- a/staticlibs/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/staticlibs/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -170,8 +170,11 @@
}
// Make open for use in ConnectivityServiceTest which is the only one knowing its handlers.
- @JvmOverloads
- open fun assertNoCallback(timeoutMs: Long = defaultTimeoutMs) {
+ // TODO : remove the necessity to overload this, remove the open qualifier, and give a
+ // default argument to assertNoCallback instead, possibly with @JvmOverloads if necessary.
+ open fun assertNoCallback() = assertNoCallback(defaultTimeoutMs)
+
+ fun assertNoCallback(timeoutMs: Long) {
val cb = history.poll(timeoutMs)
if (null != cb) fail("Expected no callback but got $cb")
}
@@ -200,6 +203,14 @@
assertNotNull(it, "Callback ${T::class} not received within ${timeoutMs}ms")
} as T
+ fun <T : CallbackEntry> eventuallyExpect(
+ type: KClass<T>,
+ timeoutMs: Long = defaultTimeoutMs,
+ predicate: (T: CallbackEntry) -> Boolean = { true }
+ ) = history.poll(timeoutMs) { type.java.isInstance(it) && predicate(it) }.also {
+ assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms")
+ } as T
+
// TODO (b/157405399) straighten and unify the method names
inline fun <reified T : CallbackEntry> eventuallyExpectOrNull(
timeoutMs: Long = defaultTimeoutMs,
diff --git a/staticlibs/hostdevice/com/android/testutils/ConcurrentUtils.kt b/staticlibs/hostdevice/com/android/testutils/ConcurrentUtils.kt
index a365af5..af4f96d 100644
--- a/staticlibs/hostdevice/com/android/testutils/ConcurrentUtils.kt
+++ b/staticlibs/hostdevice/com/android/testutils/ConcurrentUtils.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:JvmName("ConcurrentUtils")
+
package com.android.testutils
import java.util.concurrent.CountDownLatch
diff --git a/staticlibs/hostdevice/com/android/testutils/MiscAsserts.kt b/staticlibs/hostdevice/com/android/testutils/MiscAsserts.kt
index 4e86360..09126d7 100644
--- a/staticlibs/hostdevice/com/android/testutils/MiscAsserts.kt
+++ b/staticlibs/hostdevice/com/android/testutils/MiscAsserts.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:JvmName("MiscAsserts")
+
package com.android.testutils
import com.android.testutils.ExceptionUtils.ThrowingRunnable
diff --git a/staticlibs/hostdevice/com/android/testutils/PacketFilter.kt b/staticlibs/hostdevice/com/android/testutils/PacketFilter.kt
index cd8d6a5..7c615d0 100644
--- a/staticlibs/hostdevice/com/android/testutils/PacketFilter.kt
+++ b/staticlibs/hostdevice/com/android/testutils/PacketFilter.kt
@@ -16,19 +16,28 @@
package com.android.testutils
+import java.net.Inet4Address
import java.util.function.Predicate
const val ETHER_TYPE_OFFSET = 12
const val ETHER_HEADER_LENGTH = 14
const val IPV4_PROTOCOL_OFFSET = ETHER_HEADER_LENGTH + 9
const val IPV4_CHECKSUM_OFFSET = ETHER_HEADER_LENGTH + 10
+const val IPV4_DST_OFFSET = ETHER_HEADER_LENGTH + 16
const val IPV4_HEADER_LENGTH = 20
const val IPV4_UDP_OFFSET = ETHER_HEADER_LENGTH + IPV4_HEADER_LENGTH
-const val BOOTP_OFFSET = IPV4_UDP_OFFSET + 8
+const val IPV4_UDP_SRCPORT_OFFSET = IPV4_UDP_OFFSET
+const val IPV4_UDP_DSTPORT_OFFSET = IPV4_UDP_OFFSET + 2
+const val UDP_HEADER_LENGTH = 8
+const val BOOTP_OFFSET = IPV4_UDP_OFFSET + UDP_HEADER_LENGTH
const val BOOTP_TID_OFFSET = BOOTP_OFFSET + 4
const val BOOTP_CLIENT_MAC_OFFSET = BOOTP_OFFSET + 28
const val DHCP_OPTIONS_OFFSET = BOOTP_OFFSET + 240
+const val ARP_OPCODE_OFFSET = ETHER_HEADER_LENGTH + 6
+const val ARP_SENDER_MAC_OFFSET = ETHER_HEADER_LENGTH + 8
+const val ARP_TARGET_IPADDR_OFFSET = ETHER_HEADER_LENGTH + 24
+
/**
* A [Predicate] that matches a [ByteArray] if it contains the specified [bytes] at the specified
* [offset].
@@ -48,12 +57,29 @@
}
/**
+ * A [Predicate] that matches ethernet-encapped packets sent to the specified IPv4 destination.
+ */
+class IPv4DstFilter(dst: Inet4Address) : Predicate<ByteArray> {
+ private val impl = OffsetFilter(IPV4_DST_OFFSET, *dst.address)
+ override fun test(t: ByteArray) = impl.test(t)
+}
+
+/**
+ * A [Predicate] that matches ethernet-encapped ARP requests.
+ */
+class ArpRequestFilter : Predicate<ByteArray> {
+ private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x06 /* ARP */)
+ .and(OffsetFilter(ARP_OPCODE_OFFSET, 0x00, 0x01 /* request */))
+ override fun test(t: ByteArray) = impl.test(t)
+}
+
+/**
* A [Predicate] that matches ethernet-encapped DHCP packets sent from a DHCP client.
*/
class DhcpClientPacketFilter : Predicate<ByteArray> {
private val impl = IPv4UdpFilter()
- .and(OffsetFilter(IPV4_UDP_OFFSET /* source port */, 0x00, 0x44 /* 68 */))
- .and(OffsetFilter(IPV4_UDP_OFFSET + 2 /* dest port */, 0x00, 0x43 /* 67 */))
+ .and(OffsetFilter(IPV4_UDP_SRCPORT_OFFSET, 0x00, 0x44 /* 68 */))
+ .and(OffsetFilter(IPV4_UDP_DSTPORT_OFFSET, 0x00, 0x43 /* 67 */))
override fun test(t: ByteArray) = impl.test(t)
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PacketReaderTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/PacketReaderTest.java
index e3af97e..046a1d9 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/PacketReaderTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PacketReaderTest.java
@@ -24,7 +24,7 @@
import static android.system.OsConstants.SOL_SOCKET;
import static android.system.OsConstants.SO_SNDTIMEO;
-import static com.android.testutils.MiscAssertsKt.assertThrows;
+import static com.android.testutils.MiscAsserts.assertThrows;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;