EthernetTetheringTest: add testIcmpv4Echo

Bug: 237369591
Test: atest EthernetTetheringTest
Change-Id: I96278c17ce97b63860b9379675c6ac2941b4016f
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 6b9f916..e694907 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -1547,7 +1547,7 @@
     // TODO: move ICMPv4 packet build function to common utilis.
     @NonNull
     private ByteBuffer buildIcmpEchoPacketV4(
-            @NonNull final MacAddress srcMac, @NonNull final MacAddress dstMac,
+            @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
             @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp,
             int type, short id, short seq) throws Exception {
         if (type != ICMP_ECHO && type != ICMP_ECHOREPLY) {
@@ -1560,30 +1560,41 @@
         payload.putShort(seq);
         payload.rewind();
 
-        final int etherHeaderLen = Struct.getSize(EthernetHeader.class);
+        final boolean hasEther = (srcMac != null && dstMac != null);
+        final int etherHeaderLen = hasEther ? Struct.getSize(EthernetHeader.class) : 0;
         final int ipv4HeaderLen = Struct.getSize(Ipv4Header.class);
         final int Icmpv4HeaderLen = Struct.getSize(Icmpv4Header.class);
         final int payloadLen = payload.limit();
         final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv4HeaderLen
                 + Icmpv4HeaderLen + payloadLen);
 
-        final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, ETHER_TYPE_IPV4);
+        // [1] Ethernet header
+        if (hasEther) {
+            final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, ETHER_TYPE_IPV4);
+            ethHeader.writeToByteBuffer(packet);
+        }
+
+        // [2] IP header
         final Ipv4Header ipv4Header = new Ipv4Header(TYPE_OF_SERVICE,
                 (short) 0 /* totalLength, calculate later */, ID,
                 FLAGS_AND_FRAGMENT_OFFSET, TIME_TO_LIVE, (byte) IPPROTO_ICMP,
                 (short) 0 /* checksum, calculate later */, srcIp, dstIp);
+        ipv4Header.writeToByteBuffer(packet);
+
+        // [3] ICMP header
         final Icmpv4Header icmpv4Header = new Icmpv4Header((byte) type, ICMPECHO_CODE,
                 (short) 0 /* checksum, calculate later */);
-
-        ethHeader.writeToByteBuffer(packet);
-        ipv4Header.writeToByteBuffer(packet);
         icmpv4Header.writeToByteBuffer(packet);
+
+        // [4] Payload
         packet.put(payload);
         packet.flip();
 
-        // Used for updating IP header fields. IPv4 header offset in buffer equals ethernet header
-        // length because IPv4 header is located next to ethernet header.
-        final int ipv4HeaderOffset = etherHeaderLen;
+        // [5] Finalize packet
+        // Used for updating IP header fields. If there is Ehternet header, IPv4 header offset
+        // in buffer equals ethernet header length because IPv4 header is located next to ethernet
+        // header. Otherwise, IPv4 header offset is 0.
+        final int ipv4HeaderOffset = hasEther ? etherHeaderLen : 0;
 
         // Populate the IPv4 totalLength field.
         packet.putShort(ipv4HeaderOffset + IPV4_LENGTH_OFFSET,
@@ -1600,6 +1611,43 @@
         return packet;
     }
 
+    @NonNull
+    private ByteBuffer buildIcmpEchoPacketV4(@NonNull final Inet4Address srcIp,
+            @NonNull final Inet4Address dstIp, int type, short id, short seq)
+            throws Exception {
+        return buildIcmpEchoPacketV4(null /* srcMac */, null /* dstMac */, srcIp, dstIp,
+                type, id, seq);
+    }
+
+    @Test
+    public void testIcmpv4Echo() throws Exception {
+        final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
+                toList(TEST_IP4_DNS));
+        final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
+
+        // TODO: remove the connectivity verification for upstream connected notification race.
+        // See the same reason in runUdp4Test().
+        probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
+
+        final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */,
+                tethered.routerMacAddr /* dstMac */, tethered.ipv4Addr /* srcIp */,
+                REMOTE_IP4_ADDR /* dstIp */, ICMP_ECHO, ICMPECHO_ID, ICMPECHO_SEQ);
+        tester.verifyUpload(request, p -> {
+            Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+
+            return isExpectedIcmpPacket(p, false /* hasEth */, true /* isIpv4 */, ICMP_ECHO);
+        });
+
+        final ByteBuffer reply = buildIcmpEchoPacketV4(REMOTE_IP4_ADDR /* srcIp*/,
+                (Inet4Address) TEST_IP4_ADDR.getAddress() /* dstIp */, ICMP_ECHOREPLY, ICMPECHO_ID,
+                ICMPECHO_SEQ);
+        tester.verifyDownload(reply, p -> {
+            Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+
+            return isExpectedIcmpPacket(p, true /* hasEth */, true /* isIpv4 */, ICMP_ECHOREPLY);
+        });
+    }
+
     // TODO: support R device. See b/234727688.
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.R)