Use JBSMATCH opcode for ethernet address matching

This commit refactors the code to use the JBSMATCH opcode when possible
for ethernet address matching, improving code density.

Before:
03-21 14:43:07.523 28553 28566 I ApfFilterTest: all feature on, program size: 4503

After:
03-21 16:43:22.941  1490  1510 I ApfFilterTest: all feature on, program size: 4446

Test: TH
Change-Id: Id2f20bdfe2da579b3f1d8e441533532aaee2816d
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 7c6692f..bde487e 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -1830,8 +1830,8 @@
 
         // Pass if non-broadcast reply.
         // This also accepts multicast arp, but we assume those don't exist.
-        gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
-        gen.addCountAndPassIfBytesAtR0NotEqual(ETHER_BROADCAST, PASSED_ARP_UNICAST_REPLY);
+        gen.addCountAndPassIfBytesAtOffsetNotEqual(ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST,
+                PASSED_ARP_UNICAST_REPLY);
 
         // It is a broadcast reply.
         if (mIPv4Address == null) {
@@ -1897,8 +1897,8 @@
         // the future, such packets will likely be dropped by multicast filters.
         // Since the device may have packet forwarding enabled, APF needs to pass any received
         // unicast IPv4 ping not destined for the device's IP address to the kernel.
-        gen.addLoadImmediate(R0, ETHER_DST_ADDR_OFFSET)
-                .addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipIpv4PingFilter)
+        gen.addJumpIfBytesAtOffsetNotEqual(
+                ETH_DEST_ADDR_OFFSET, mHardwareAddress, skipIpv4PingFilter)
                 .addLoadImmediate(R0, IPV4_DEST_ADDR_OFFSET)
                 .addJumpIfBytesAtR0NotEqual(mIPv4Address, skipIpv4PingFilter);
 
@@ -1979,11 +1979,11 @@
         // address for IPv4 mDNS packet) or the device's MAC address, skip filtering.
         // We need to check both the mDNS multicast MAC address and the device's MAC address
         // because multicast to unicast conversion might have occurred.
-        gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET)
-                .addJumpIfBytesAtR0EqualsNoneOf(
-                        List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS),
-                        skipMdnsFilter
-                );
+        gen.addJumpIfBytesAtOffsetEqualsNoneOf(
+                ETH_DEST_ADDR_OFFSET,
+                List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS),
+                skipMdnsFilter
+        );
 
         // Ignore packets with IPv4 options (header size not equal to 20) as they are rare.
         gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE)
@@ -2185,8 +2185,8 @@
             // Otherwise, this is an IPv4 unicast, pass
             // If L2 broadcast packet, drop.
             // TODO: can we invert this condition to fall through to the common pass case below?
-            gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
-            gen.addCountAndPassIfBytesAtR0NotEqual(ETHER_BROADCAST, PASSED_IPV4_UNICAST);
+            gen.addCountAndPassIfBytesAtOffsetNotEqual(ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST,
+                    PASSED_IPV4_UNICAST);
             gen.addCountAndDrop(DROPPED_IPV4_L2_BROADCAST);
         }
 
@@ -2329,8 +2329,8 @@
         // used by processes other than clatd. This is because APF cannot reliably detect signal
         // on when IPV6_{JOIN,LEAVE}_ANYCAST is triggered.
         final List<byte[]> allMACs = getKnownMacAddresses();
-        v6Gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET)
-                .addCountAndDropIfBytesAtR0EqualsNoneOf(allMACs, DROPPED_IPV6_NS_OTHER_HOST);
+        v6Gen.addCountAndDropIfBytesAtOffsetEqualsNoneOf(ETH_DEST_ADDR_OFFSET, allMACs,
+                DROPPED_IPV6_NS_OTHER_HOST);
 
         // Dst IPv6 address check:
         final List<byte[]> allSuffixes = getSolicitedNodeMcastAddressSuffix(allIPv6Addrs);
@@ -2444,11 +2444,11 @@
         // address for IPv6 mDNS packet) or the device's MAC address, skip filtering.
         // We need to check both the mDNS multicast MAC address and the device's MAC address
         // because multicast to unicast conversion might have occurred.
-        gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET)
-                .addJumpIfBytesAtR0EqualsNoneOf(
-                        List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS),
-                        skipMdnsFilter
-                );
+        gen.addJumpIfBytesAtOffsetEqualsNoneOf(
+                ETH_DEST_ADDR_OFFSET,
+                List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS),
+                skipMdnsFilter
+        );
 
         // Skip filtering if the packet is not an IPv6 UDP packet.
         gen.addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET)
@@ -2509,8 +2509,8 @@
                 true /* includeNonTentative */,
                 false /* includeTentative */,
                 false /* includeAnycast */);
-        gen.addLoadImmediate(R0, ETHER_DST_ADDR_OFFSET)
-                .addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipPing6Offload)
+        gen.addJumpIfBytesAtOffsetNotEqual(
+                ETHER_DST_ADDR_OFFSET, mHardwareAddress, skipPing6Offload)
                 .addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET)
                 .addJumpIfBytesAtR0EqualsNoneOf(nonTentativeIPv6Addrs, skipPing6Offload);
 
@@ -3604,8 +3604,8 @@
             // Pass unicast TDLS packet but drop non-unicast TDLS packet.
             short skipTDLScheck = gen.getUniqueLabel();
             gen.addJumpIfR0NotEquals(0x890DL, skipTDLScheck)
-                    .addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET)
-                    .addCountAndDropIfBytesAtR0NotEqual(mHardwareAddress, DROPPED_NON_UNICAST_TDLS)
+                    .addCountAndDropIfBytesAtOffsetNotEqual(
+                            ETH_DEST_ADDR_OFFSET, mHardwareAddress, DROPPED_NON_UNICAST_TDLS)
                     .addCountAndPass(PASSED_NON_IP_UNICAST)
                     .defineLabel(skipTDLScheck);
 
@@ -3650,8 +3650,8 @@
         gen.addJumpIfR0Equals(ETH_P_IPV6, ipv6FilterLabel);
 
         // Drop non-IP non-ARP broadcasts, pass the rest
-        gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
-        gen.addCountAndPassIfBytesAtR0NotEqual(ETHER_BROADCAST, PASSED_NON_IP_UNICAST);
+        gen.addCountAndPassIfBytesAtOffsetNotEqual(ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST,
+                PASSED_NON_IP_UNICAST);
         gen.addCountAndDrop(DROPPED_ETH_BROADCAST);
 
         // Add IPv6 filters:
@@ -3739,11 +3739,17 @@
     }
 
     void preloadData(ApfV61GeneratorBase<?> gen) throws IllegalInstructionException {
+        final List<byte[]> preloadedMacAddress = getKnownMacAddresses();
         final List<byte[]> preloadedIPv6Address = getIpv6Addresses(true /* includeNonTentative */,
                 true /* includeTentative */, true /* includeAnycast */);
-        final int preloadDataSize = preloadedIPv6Address.size() * 16;
+        final int preloadDataSize =
+                preloadedIPv6Address.size() * 16 + preloadedMacAddress.size() * 6;
         final byte[] preloadData = new byte[preloadDataSize];
         int offset = 0;
+        for (byte[] addr : preloadedMacAddress) {
+            System.arraycopy(addr, 0, preloadData, offset, 6);
+            offset += 6;
+        }
         for (byte[] addr : preloadedIPv6Address) {
             System.arraycopy(addr, 0, preloadData, offset, 16);
             offset += 16;
diff --git a/src/android/net/apf/ApfV4GeneratorBase.java b/src/android/net/apf/ApfV4GeneratorBase.java
index f142e31..fb929dc 100644
--- a/src/android/net/apf/ApfV4GeneratorBase.java
+++ b/src/android/net/apf/ApfV4GeneratorBase.java
@@ -482,6 +482,17 @@
     }
 
     /**
+     * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
+     * packet at an offset specified by {@code offset} don't match {@code bytes}.
+     * This method needs to be non-final because APFv4 and APFv6 share the same implementation,
+     * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction.
+     */
+    public Type addJumpIfBytesAtOffsetNotEqual(int offset, @NonNull byte[] bytes, short tgt)
+            throws IllegalInstructionException {
+        return addLoadImmediate(R0, offset).addJumpIfBytesAtR0NotEqual(bytes, tgt);
+    }
+
+    /**
      * Add instructions to the end of the program to increase counter and drop packet if the
      * bytes of the packet at an offset specified by register0 don't match {@code bytes}.
      * WARNING: may modify R1
@@ -499,6 +510,28 @@
 
     /**
      * Add instructions to the end of the program to increase counter and drop packet if the
+     * bytes of the packet at an offset specified by {@code offset} don't match {@code bytes}.
+     * This method needs to be non-final because APFv4 and APFv6 share the same implementation,
+     * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction.
+     */
+    public Type addCountAndDropIfBytesAtOffsetNotEqual(int offset, byte[] bytes,
+            ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+        return addLoadImmediate(R0, offset).addCountAndDropIfBytesAtR0NotEqual(bytes, cnt);
+    }
+
+    /**
+     * Add instructions to the end of the program to increase counter and pass packet if the
+     * bytes of the packet at an offset specified by {@code offset} don't match {@code bytes}.
+     * This method needs to be non-final because APFv4 and APFv6 share the same implementation,
+     * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction.
+     */
+    public Type addCountAndPassIfBytesAtOffsetNotEqual(int offset, byte[] bytes,
+            ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+        return addLoadImmediate(R0, offset).addCountAndPassIfBytesAtR0NotEqual(bytes, cnt);
+    }
+
+    /**
+     * Add instructions to the end of the program to increase counter and drop packet if the
      * bytes of the packet at an offset specified by register0 match {@code bytes}.
      * WARNING: may modify R1
      */
diff --git a/src/android/net/apf/ApfV61GeneratorBase.java b/src/android/net/apf/ApfV61GeneratorBase.java
index 014d893..c686b71 100644
--- a/src/android/net/apf/ApfV61GeneratorBase.java
+++ b/src/android/net/apf/ApfV61GeneratorBase.java
@@ -325,6 +325,24 @@
         return addJumpIfBytesAtOffsetEqualsNoneOf(offset, bytesList, cnt.getJumpPassLabel());
     }
 
+    @Override
+    public Type addCountAndPassIfBytesAtOffsetNotEqual(int offset, byte[] bytes,
+            ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+        return addJumpIfBytesAtOffsetEqualsNoneOf(offset, List.of(bytes), cnt.getJumpPassLabel());
+    }
+
+    @Override
+    public Type addCountAndDropIfBytesAtOffsetNotEqual(int offset, byte[] bytes,
+            ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+        return addJumpIfBytesAtOffsetEqualsNoneOf(offset, List.of(bytes), cnt.getJumpDropLabel());
+    }
+
+    @Override
+    public Type addJumpIfBytesAtOffsetNotEqual(int offset, @NonNull byte[] bytes, short tgt)
+            throws IllegalInstructionException {
+        return addJumpIfBytesAtOffsetEqualsNoneOf(offset, List.of(bytes), tgt);
+    }
+
     /**
      * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
      * payload's DNS questions contain the QNAMEs specified in {@code qnames} and qtype
diff --git a/src/android/net/apf/ApfV6Generator.java b/src/android/net/apf/ApfV6Generator.java
index 045423b..07bd191 100644
--- a/src/android/net/apf/ApfV6Generator.java
+++ b/src/android/net/apf/ApfV6Generator.java
@@ -262,6 +262,18 @@
     }
 
     @Override
+    public ApfV6Generator addJumpIfBytesAtOffsetEqualsAnyOf(int offset, List<byte[]> bytesList,
+            short tgt) throws IllegalInstructionException {
+        return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsAnyOf(bytesList, tgt);
+    }
+
+    @Override
+    public ApfV6Generator addJumpIfBytesAtOffsetEqualsNoneOf(int offset, List<byte[]> bytesList,
+            short tgt) throws IllegalInstructionException {
+        return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsNoneOf(bytesList, tgt);
+    }
+
+    @Override
     public ApfV6Generator addCountAndDropIfR0IsNoneOf(@NonNull Set<Long> values,
             ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
         if (values.isEmpty()) {
diff --git a/src/android/net/apf/ApfV6GeneratorBase.java b/src/android/net/apf/ApfV6GeneratorBase.java
index 68a29c5..90f0a28 100644
--- a/src/android/net/apf/ApfV6GeneratorBase.java
+++ b/src/android/net/apf/ApfV6GeneratorBase.java
@@ -552,6 +552,21 @@
         return addJumpIfBytesAtR0EqualsHelper(bytesList, tgt, false /* jumpOnMatch */);
     }
 
+    /**
+     * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
+     * packet at an offset specified by {@code offset} match any of the elements in
+     * {@code bytesSet}.
+     */
+    public abstract Type addJumpIfBytesAtOffsetEqualsAnyOf(int offset,
+            @NonNull List<byte[]> bytesList, short tgt) throws IllegalInstructionException;
+
+    /**
+     * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
+     * packet at an offset specified by {@code offset} match none of the elements in
+     * {@code bytesSet}.
+     */
+    public abstract Type addJumpIfBytesAtOffsetEqualsNoneOf(int offset,
+            @NonNull List<byte[]> bytesList, short tgt) throws IllegalInstructionException;
 
     /**
      * Check if the byte is valid dns character: A-Z,0-9,-,_,%,@
diff --git a/tests/unit/src/android/net/apf/ApfFilterTest.kt b/tests/unit/src/android/net/apf/ApfFilterTest.kt
index 2d20ed6..6fcf641 100644
--- a/tests/unit/src/android/net/apf/ApfFilterTest.kt
+++ b/tests/unit/src/android/net/apf/ApfFilterTest.kt
@@ -22,6 +22,8 @@
 import android.net.MacAddress
 import android.net.NattKeepalivePacketDataParcelable
 import android.net.TcpKeepalivePacketDataParcelable
+import android.net.apf.ApfConstants.ETH_MULTICAST_MDNS_V4_MAC_ADDRESS
+import android.net.apf.ApfConstants.ETH_MULTICAST_MDNS_V6_MAC_ADDRESS
 import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_NON_IPV4
 import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_OTHER_HOST
 import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REPLY_SPA_NO_HOST
@@ -240,6 +242,8 @@
         intArrayOf(0x33, 0x33, 0xff, 0x55, 0x66, 0x77).map { it.toByte() }.toByteArray(),
         // 33:33:ff:bb:cc:dd
         intArrayOf(0x33, 0x33, 0xff, 0xbb, 0xcc, 0xdd).map { it.toByte() }.toByteArray(),
+        ETH_MULTICAST_MDNS_V4_MAC_ADDRESS,
+        ETH_MULTICAST_MDNS_V6_MAC_ADDRESS
     )
 
     // Using scapy to generate payload:
@@ -5797,7 +5801,8 @@
             )
             assertThat(program.size).isLessThan(apfRamSize + 1)
             assertThat(program).isNotEqualTo(ByteArray(apfRamSize) { 0 })
-            val step = Random.nextInt(1, 16)
+            // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms'
+            val step = Random.nextInt(1, 64)
             apfRamSize += step
         }
     }
@@ -5817,7 +5822,8 @@
             val availableRam = apfRamSize - ApfCounterTracker.Counter.totalSize()
             assertThat(program.size).isLessThan(availableRam + 1)
             assertThat(program).isNotEqualTo(ByteArray(availableRam) { 0 })
-            val step = Random.nextInt(1, 16)
+            // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms'
+            val step = Random.nextInt(1, 64)
             apfRamSize += step
         }
     }
@@ -5836,7 +5842,8 @@
             val availableRam = apfRamSize - ApfCounterTracker.Counter.totalSize()
             assertThat(program.size).isLessThan(availableRam + 1)
             assertThat(program).isNotEqualTo(ByteArray(availableRam) { 0 })
-            val step = Random.nextInt(1, 16)
+            // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms'
+            val step = Random.nextInt(1, 64)
             apfRamSize += step
         }
     }