Move TapPacketReader into its own utility class.

This class is generally useful for tests that manipulate packets
using a TestNetworkInterface. It will be used in upcoming
tethering tests. Also make it take a FileDescriptor instead of a
ParcelFileDescriptor, since that is more generally more useful.

As part of this change, create a filegroup of files that the test
utilities can depend on.

Bug: 150644681
Test: atest NetworkStackIntegrationTests:IpClientIntegrationTest
Change-Id: I73e9c1919956aa665a8cccec11d3444e486426ec
diff --git a/common/moduleutils/Android.bp b/common/moduleutils/Android.bp
index d0dd584..81475d2 100644
--- a/common/moduleutils/Android.bp
+++ b/common/moduleutils/Android.bp
@@ -62,3 +62,15 @@
     ],
     visibility: ["//frameworks/base/packages/Tethering"],
 }
+
+// Utility sources used by test libraries.
+// This is its own group to limit indiscriminate dependency of test code on production code.
+// TODO: move these classes and NetworkStack/tests/lib to frameworks/libs/net, and remove this.
+filegroup {
+    name: "net-module-utils-srcs-for-tests",
+    visibility: ["//packages/modules/NetworkStack/tests/lib"],
+    srcs: [
+        "src/android/net/util/FdEventsReader.java",
+        "src/android/net/util/PacketReader.java",
+    ],
+}
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
index 97da9fb..0fa6266 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
@@ -109,19 +109,16 @@
 import android.net.util.InterfaceParams;
 import android.net.util.IpUtils;
 import android.net.util.NetworkStackUtils;
-import android.net.util.PacketReader;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.system.ErrnoException;
 import android.system.Os;
 
-import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -134,6 +131,7 @@
 import com.android.server.NetworkStackService.NetworkStackServiceManager;
 import com.android.server.connectivity.ipmemorystore.IpMemoryStoreService;
 import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.TapPacketReader;
 
 import org.junit.After;
 import org.junit.Before;
@@ -145,7 +143,6 @@
 import org.mockito.Spy;
 
 import java.io.FileDescriptor;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.net.Inet4Address;
 import java.net.InetAddress;
@@ -159,8 +156,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Random;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Tests for IpClient.
@@ -192,6 +187,7 @@
     private HandlerThread mPacketReaderThread;
     private Handler mHandler;
     private TapPacketReader mPacketReader;
+    private FileDescriptor mTapFd;
     private IpClient mIpc;
     private Dependencies mDependencies;
     private byte[] mClientMac;
@@ -238,46 +234,6 @@
     };
     private static final byte TEST_VENDOR_SPECIFIC_TYPE = 0x06;
 
-    private static class TapPacketReader extends PacketReader {
-        private final ParcelFileDescriptor mTapFd;
-        private final LinkedBlockingQueue<byte[]> mReceivedPackets =
-                new LinkedBlockingQueue<byte[]>();
-
-        TapPacketReader(Handler h, ParcelFileDescriptor tapFd) {
-            super(h, DATA_BUFFER_LEN);
-            mTapFd = tapFd;
-        }
-
-        @Override
-        protected FileDescriptor createFd() {
-            return mTapFd.getFileDescriptor();
-        }
-
-        @Override
-        protected void handlePacket(byte[] recvbuf, int length) {
-            final byte[] newPacket = Arrays.copyOf(recvbuf, length);
-            try {
-                mReceivedPackets.put(newPacket);
-            } catch (InterruptedException e) {
-                fail("fail to put the new packet in the queue");
-            }
-        }
-
-        /**
-         * Get the next packet that was received on the interface.
-         *
-         */
-        @Nullable
-        public byte[] popPacket(long timeoutMs) {
-            try {
-                return mReceivedPackets.poll(timeoutMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                // Fall through
-            }
-            return null;
-        }
-    }
-
     private class Dependencies extends IpClient.Dependencies {
         private boolean mIsDhcpLeaseCacheEnabled;
         private boolean mIsDhcpRapidCommitEnabled;
@@ -411,6 +367,7 @@
     public void tearDown() throws Exception {
         if (mPacketReader != null) {
             mHandler.post(() -> mPacketReader.stop()); // Also closes the socket
+            mTapFd = null;
         }
         if (mPacketReaderThread != null) {
             mPacketReaderThread.quitSafely();
@@ -440,8 +397,8 @@
         mPacketReaderThread.start();
         mHandler = mPacketReaderThread.getThreadHandler();
 
-        final ParcelFileDescriptor tapFd = iface.getFileDescriptor();
-        mPacketReader = new TapPacketReader(mHandler, tapFd);
+        mTapFd = iface.getFileDescriptor().getFileDescriptor();
+        mPacketReader = new TapPacketReader(mHandler, mTapFd, DATA_BUFFER_LEN);
         mHandler.post(() -> mPacketReader.start());
     }
 
@@ -532,21 +489,12 @@
             false /* broadcast */, "duplicated request IP address");
     }
 
-    private void sendResponse(final ByteBuffer packet) throws IOException {
-        try (FileOutputStream out = new FileOutputStream(mPacketReader.createFd())) {
-            byte[] packetBytes = new byte[packet.limit()];
-            packet.get(packetBytes);
-            packet.flip();  // So we can reuse it in the future.
-            out.write(packetBytes);
-        }
-    }
-
     private void sendArpReply(final byte[] clientMac) throws IOException {
         final ByteBuffer packet = ArpPacket.buildArpPacket(clientMac /* dst */,
                 SERVER_MAC /* src */, INADDR_ANY.getAddress() /* target IP */,
                 clientMac /* target HW address */, CLIENT_ADDR.getAddress() /* sender IP */,
                 (short) ARP_REPLY);
-        sendResponse(packet);
+        mPacketReader.sendResponse(packet);
     }
 
     private void sendArpProbe() throws IOException {
@@ -554,7 +502,7 @@
                 SERVER_MAC /* src */, CLIENT_ADDR.getAddress() /* target IP */,
                 new byte[ETHER_ADDR_LEN] /* target HW address */,
                 INADDR_ANY.getAddress() /* sender IP */, (short) ARP_REQUEST);
-        sendResponse(packet);
+        mPacketReader.sendResponse(packet);
     }
 
     private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled,
@@ -667,18 +615,18 @@
             packetList.add(packet);
             if (packet instanceof DhcpDiscoverPacket) {
                 if (shouldReplyRapidCommitAck) {
-                    sendResponse(buildDhcpAckPacket(packet, leaseTimeSec, (short) mtu,
+                    mPacketReader.sendResponse(buildDhcpAckPacket(packet, leaseTimeSec, (short) mtu,
                               true /* rapidCommit */, captivePortalApiUrl));
                 } else {
-                    sendResponse(buildDhcpOfferPacket(packet, leaseTimeSec, (short) mtu,
-                            captivePortalApiUrl));
+                    mPacketReader.sendResponse(buildDhcpOfferPacket(packet, leaseTimeSec,
+                            (short) mtu, captivePortalApiUrl));
                 }
             } else if (packet instanceof DhcpRequestPacket) {
                 final ByteBuffer byteBuffer = isSuccessLease
                         ? buildDhcpAckPacket(packet, leaseTimeSec, (short) mtu,
                                 false /* rapidCommit */, captivePortalApiUrl)
                         : buildDhcpNakPacket(packet);
-                sendResponse(byteBuffer);
+                mPacketReader.sendResponse(byteBuffer);
             } else {
                 fail("invalid DHCP packet");
             }
@@ -777,7 +725,7 @@
             assertEquals(NetworkInterface.getByName(mIfaceName).getMTU(), mtu);
         }
 
-        if (shouldRemoveTapInterface) removeTapInterface(mPacketReader.createFd());
+        if (shouldRemoveTapInterface) removeTapInterface(mTapFd);
         try {
             mIpc.shutdown();
             awaitIpClientShutdown();
@@ -845,12 +793,12 @@
 
         final short mtu = (short) TEST_DEFAULT_MTU;
         if (!shouldReplyRapidCommitAck) {
-            sendResponse(buildDhcpOfferPacket(packet, TEST_LEASE_DURATION_S, mtu,
+            mPacketReader.sendResponse(buildDhcpOfferPacket(packet, TEST_LEASE_DURATION_S, mtu,
                     null /* captivePortalUrl */));
             packet = getNextDhcpPacket();
             assertTrue(packet instanceof DhcpRequestPacket);
         }
-        sendResponse(buildDhcpAckPacket(packet, TEST_LEASE_DURATION_S, mtu,
+        mPacketReader.sendResponse(buildDhcpAckPacket(packet, TEST_LEASE_DURATION_S, mtu,
                 shouldReplyRapidCommitAck, null /* captivePortalUrl */));
 
         if (!shouldAbortPreconnection) {
@@ -1126,7 +1074,7 @@
     @Test
     public void testRestoreInitialInterfaceMtu_NotFoundInterfaceWhenStartingProvisioning()
             throws Exception {
-        removeTapInterface(mPacketReader.createFd());
+        removeTapInterface(mTapFd);
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
                 .withoutIpReachabilityMonitor()
                 .withoutIPv6()
@@ -1284,7 +1232,7 @@
         ByteBuffer ra = buildRaPacket(pio, rdnss1, rdnss2);
 
         waitForRouterSolicitation();
-        sendResponse(ra);
+        mPacketReader.sendResponse(ra);
 
         ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
         verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture());
@@ -1299,7 +1247,7 @@
         // If the RDNSS lifetime is above the minimum, the DNS server is accepted.
         rdnss1 = buildRdnssOption(68, lowlifeDnsServer);
         ra = buildRaPacket(pio, rdnss1, rdnss2);
-        sendResponse(ra);
+        mPacketReader.sendResponse(ra);
         verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(captor.capture());
         lp = captor.getValue();
         assertNotNull(lp);
@@ -1312,7 +1260,7 @@
         rdnss1 = buildRdnssOption(0, dnsServer);
         rdnss2 = buildRdnssOption(0, lowlifeDnsServer);
         ra = buildRaPacket(pio, rdnss1, rdnss2);
-        sendResponse(ra);
+        mPacketReader.sendResponse(ra);
 
         verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningFailure(captor.capture());
         lp = captor.getValue();
@@ -1546,8 +1494,8 @@
 
         // Send Offer and handle Request -> Ack
         final String serverSentUrl = serverSendsOption ? TEST_CAPTIVE_PORTAL_URL : null;
-        sendResponse(buildDhcpOfferPacket(discover, TEST_LEASE_DURATION_S, (short) TEST_DEFAULT_MTU,
-                serverSentUrl));
+        mPacketReader.sendResponse(buildDhcpOfferPacket(discover, TEST_LEASE_DURATION_S,
+                (short) TEST_DEFAULT_MTU, serverSentUrl));
         final int testMtu = 1345;
         handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
                 false /* isDhcpRapidCommitEnabled */, testMtu,
diff --git a/tests/lib/Android.bp b/tests/lib/Android.bp
index 249088f..d43243f 100644
--- a/tests/lib/Android.bp
+++ b/tests/lib/Android.bp
@@ -32,8 +32,12 @@
     srcs: [
         "src/**/*.java",
         "src/**/*.kt",
+        ":net-module-utils-srcs-for-tests",
     ],
     defaults: ["lib_mockito_extended"],
+    libs: [
+        "androidx.annotation_annotation",
+    ],
     static_libs: [
         "net-tests-utils-multivariant",
     ],
diff --git a/tests/lib/src/com/android/testutils/TapPacketReader.java b/tests/lib/src/com/android/testutils/TapPacketReader.java
new file mode 100644
index 0000000..c68e5a5
--- /dev/null
+++ b/tests/lib/src/com/android/testutils/TapPacketReader.java
@@ -0,0 +1,76 @@
+/*
+ * 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.util.PacketReader;
+import android.os.Handler;
+
+import androidx.annotation.Nullable;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class TapPacketReader extends PacketReader {
+    private final FileDescriptor mTapFd;
+    private final LinkedBlockingQueue<byte[]> mReceivedPackets = new LinkedBlockingQueue<byte[]>();
+
+    public TapPacketReader(Handler h, FileDescriptor tapFd, int maxPacketSize) {
+        super(h, maxPacketSize);
+        mTapFd = tapFd;
+    }
+
+    @Override
+    protected FileDescriptor createFd() {
+        return mTapFd;
+    }
+
+    @Override
+    protected void handlePacket(byte[] recvbuf, int length) {
+        final byte[] newPacket = Arrays.copyOf(recvbuf, length);
+        if (!mReceivedPackets.offer(newPacket)) {
+            throw new AssertionError("More than " + Integer.MAX_VALUE + " packets outstanding!");
+        }
+    }
+
+    /**
+     * Get the next packet that was received on the interface.
+     *
+     */
+    @Nullable
+    public byte[] popPacket(long timeoutMs) {
+        try {
+            return mReceivedPackets.poll(timeoutMs, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            // Fall through
+        }
+        return null;
+    }
+
+    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);
+        }
+    }
+}