Merge changes I5391fedf,I4b49f2c0,I91fd81f5 into main

* changes:
  APFv6.1: addTransmitL4() optimizations
  APFv6.1: addTransmitWithoutChecksum()
  Add JBSPTRMATCH support to APFv6.1 generator and ApfFilter
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index e738da4..7c6692f 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -2342,7 +2342,8 @@
                 .addCountAndDropIfBytesAtR0EqualsNoneOf(allSuffixes, DROPPED_IPV6_NS_OTHER_HOST)
                 .addJump(endOfIpV6DstCheck)
                 .defineLabel(notIpV6SolicitedNodeMcast)
-                .addCountAndDropIfBytesAtR0EqualsNoneOf(allIPv6Addrs, DROPPED_IPV6_NS_OTHER_HOST)
+                .addCountAndDropIfBytesAtOffsetEqualsNoneOf(
+                        IPV6_DEST_ADDR_OFFSET, allIPv6Addrs, DROPPED_IPV6_NS_OTHER_HOST)
                 .defineLabel(endOfIpV6DstCheck);
 
         // Hop limit not 255, NS requires hop limit to be 255 -> drop
@@ -2365,10 +2366,10 @@
                 true, /* includeTentative */
                 false /* includeAnycast */
         );
-        v6Gen.addLoadImmediate(R0, ICMP6_NS_TARGET_IP_OFFSET);
+
         if (!tentativeIPv6Addrs.isEmpty()) {
-            v6Gen.addCountAndPassIfBytesAtR0EqualsAnyOf(
-                    tentativeIPv6Addrs, PASSED_IPV6_ICMP);
+            v6Gen.addCountAndPassIfBytesAtOffsetEqualsAnyOf(
+                    ICMP6_NS_TARGET_IP_OFFSET, tentativeIPv6Addrs, PASSED_IPV6_ICMP);
         }
 
         final List<byte[]> nonTentativeIpv6Addrs = getIpv6Addresses(
@@ -2380,8 +2381,8 @@
             v6Gen.addCountAndDrop(DROPPED_IPV6_NS_OTHER_HOST);
             return;
         }
-        v6Gen.addCountAndDropIfBytesAtR0EqualsNoneOf(
-                nonTentativeIpv6Addrs, DROPPED_IPV6_NS_OTHER_HOST);
+        v6Gen.addCountAndDropIfBytesAtOffsetEqualsNoneOf(
+                ICMP6_NS_TARGET_IP_OFFSET, nonTentativeIpv6Addrs, DROPPED_IPV6_NS_OTHER_HOST);
 
         // if source ip is unspecified (::), it's DAD request -> pass
         v6Gen.addLoadImmediate(R0, IPV6_SRC_ADDR_OFFSET)
@@ -3737,6 +3738,21 @@
         return gen.programLengthOverEstimate() - gen.getBaseProgramSize();
     }
 
+    void preloadData(ApfV61GeneratorBase<?> gen) throws IllegalInstructionException {
+        final List<byte[]> preloadedIPv6Address = getIpv6Addresses(true /* includeNonTentative */,
+                true /* includeTentative */, true /* includeAnycast */);
+        final int preloadDataSize = preloadedIPv6Address.size() * 16;
+        final byte[] preloadData = new byte[preloadDataSize];
+        int offset = 0;
+        for (byte[] addr : preloadedIPv6Address) {
+            System.arraycopy(addr, 0, preloadData, offset, 16);
+            offset += 16;
+        }
+        if (preloadDataSize > 0) {
+            gen.addPreloadData(preloadData);
+        }
+    }
+
     /**
      * Generate and install a new filter program.
      */
@@ -3758,6 +3774,9 @@
         try {
             // Step 1: Determine how many RA filters/mDNS offloads we can fit in the program.
             ApfV4GeneratorBase<?> gen = createApfGenerator();
+            if (gen instanceof ApfV61GeneratorBase<?>) {
+                preloadData((ApfV61GeneratorBase<?>) gen);
+            }
             short labelCheckMdnsQueryPayload = gen.getUniqueLabel();
 
             emitPrologue(gen, labelCheckMdnsQueryPayload);
diff --git a/src/android/net/apf/ApfV61GeneratorBase.java b/src/android/net/apf/ApfV61GeneratorBase.java
index 212d814..014d893 100644
--- a/src/android/net/apf/ApfV61GeneratorBase.java
+++ b/src/android/net/apf/ApfV61GeneratorBase.java
@@ -15,11 +15,13 @@
  */
 package android.net.apf;
 
+import static android.net.apf.BaseApfGenerator.Rbit.Rbit0;
 import static android.net.apf.BaseApfGenerator.Rbit.Rbit1;
 import static android.net.apf.BaseApfGenerator.Register.R0;
 
 import androidx.annotation.NonNull;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
@@ -205,6 +207,124 @@
         return append(new Instruction(Opcodes.ALLOC_XMIT, Rbit1).addUnsigned(imm));
     }
 
+    @Override
+    public Type addTransmitWithoutChecksum() {
+        return append(new Instruction(Opcodes.ALLOC_XMIT, Rbit0));
+    }
+
+    @Override
+    protected boolean handleOptimizedTransmit(int ipOfs, int csumOfs, int csumStart,
+                                              int partialCsum, boolean isUdp) {
+        if (ipOfs != 14) return false;
+        int v = -1;
+        if ( isUdp && csumStart == 26 && csumOfs == 40) v = 0;  // ether/ipv4/udp
+        if (!isUdp && csumStart == 26 && csumOfs == 44) v = 1;  // ether/ipv4/tcp
+        if (!isUdp && csumStart == 34 && csumOfs == 36) v = 2;  // ether/ipv4/icmp
+        if (!isUdp && csumStart == 38 && csumOfs == 40) v = 3;  // ether/ipv4/routeralert/icmp
+        if ( isUdp && csumStart == 22 && csumOfs == 60) v = 4;  // ether/ipv6/udp
+        if (!isUdp && csumStart == 22 && csumOfs == 64) v = 5;  // ether/ipv6/tcp
+        if (!isUdp && csumStart == 22 && csumOfs == 56) v = 6;  // ether/ipv6/icmp
+        if (!isUdp && csumStart == 22 && csumOfs == 64) v = 7;  // ether/ipv6/routeralert/icmp
+        if (v < 0) return false;
+        v |= partialCsum << 3;
+        append(new Instruction(Opcodes.ALLOC_XMIT, Rbit0).addUnsigned(v));
+        return true;
+    }
+
+    private List<byte[]> addJumpIfBytesAtOffsetEqualsHelper(int offset,
+            @NonNull List<byte[]> bytesList, short tgt, boolean jumpOnMatch)
+            throws IllegalInstructionException {
+        final List<byte[]> deduplicatedList =
+                bytesList.size() == 1 ? bytesList : validateDeduplicateBytesList(bytesList);
+        if (offset < 0 || offset > 255) {
+            return deduplicatedList;
+        }
+        final int count = deduplicatedList.size();
+        final int compareLength = deduplicatedList.get(0).length;
+        if (compareLength > 16) {
+            return deduplicatedList;
+        }
+        final List<byte[]> failbackList = new ArrayList<>();
+        final List<Integer> ptrs = new ArrayList<>();
+        for (int i = 0; i < count; ++i) {
+            final byte[] bytes = deduplicatedList.get(i);
+            int relativeOffset = mInstructions.get(0).findMatchInDataBytes(bytes, 0, bytes.length);
+            if (relativeOffset < 0 || relativeOffset % 2 == 1 || relativeOffset > 510) {
+                failbackList.add(bytes);
+                continue;
+            }
+            ptrs.add(relativeOffset / 2);
+        }
+        final Rbit rbit = jumpOnMatch ? Rbit1 : Rbit0;
+        int totalPtrs = ptrs.size();
+        for (int i = 0; i < totalPtrs; i += 16) {
+            final int currentCount = Math.min(totalPtrs - i, 16);
+            final Instruction instruction = new Instruction(Opcodes.JBSPTRMATCH, rbit)
+                    .addU8(offset)
+                    .addU8((currentCount - 1) * 16 + (compareLength - 1))
+                    .setTargetLabel(tgt);
+            for (int j = 0; j < currentCount; j++) {
+                instruction.addU8(ptrs.get(i + j));
+            }
+            append(instruction);
+        }
+        return failbackList;
+    }
+
+    /**
+     * 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 bytesList}.
+     */
+    public Type addJumpIfBytesAtOffsetEqualsAnyOf(int offset, @NonNull List<byte[]> bytesList,
+            short tgt) throws IllegalInstructionException {
+        final List<byte[]> failbackList = addJumpIfBytesAtOffsetEqualsHelper(offset, bytesList, tgt,
+                true /* jumpOnMatch */);
+        if (failbackList.isEmpty()) {
+            return self();
+        }
+        return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsAnyOf(failbackList, tgt);
+    }
+
+    /**
+     * 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 bytesList}.
+     */
+    public Type addJumpIfBytesAtOffsetEqualsNoneOf(int offset, @NonNull List<byte[]> bytesList,
+            short tgt) throws IllegalInstructionException {
+        final List<byte[]> failbackList = addJumpIfBytesAtOffsetEqualsHelper(offset, bytesList, tgt,
+                false /* jumpOnMatch */);
+        if (failbackList.isEmpty()) {
+            return self();
+        }
+        return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsNoneOf(failbackList, tgt);
+    }
+
+    @Override
+    public Type addCountAndDropIfBytesAtOffsetEqualsAnyOf(int offset, List<byte[]> bytesList,
+            ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+        return addJumpIfBytesAtOffsetEqualsAnyOf(offset, bytesList, cnt.getJumpDropLabel());
+    }
+
+    @Override
+    public Type addCountAndPassIfBytesAtOffsetEqualsAnyOf(int offset, List<byte[]> bytesList,
+            ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+        return addJumpIfBytesAtOffsetEqualsAnyOf(offset, bytesList, cnt.getJumpPassLabel());
+    }
+
+    @Override
+    public Type addCountAndDropIfBytesAtOffsetEqualsNoneOf(int offset, List<byte[]> bytesList,
+            ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+        return addJumpIfBytesAtOffsetEqualsNoneOf(offset, bytesList, cnt.getJumpDropLabel());
+    }
+
+    @Override
+    public Type addCountAndPassIfBytesAtOffsetEqualsNoneOf(int offset, List<byte[]> bytesList,
+            ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+        return addJumpIfBytesAtOffsetEqualsNoneOf(offset, bytesList, cnt.getJumpPassLabel());
+    }
+
     /**
      * 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
@@ -217,4 +337,12 @@
         return append(new Instruction(ExtendedOpcodes.JDNSQMATCH2, Rbit1).setTargetLabel(tgt)
                 .addU8(qtype1).addU8(qtype2).setBytesImm(qnames));
     }
+
+    /**
+     * Preload the content of the data region.
+     */
+    public Type addPreloadData(@NonNull byte[] data) throws IllegalInstructionException {
+        mInstructions.get(0).maybeUpdateBytesImm(data, 0, data.length);
+        return self();
+    }
 }
diff --git a/src/android/net/apf/ApfV6Generator.java b/src/android/net/apf/ApfV6Generator.java
index cb95fbe..045423b 100644
--- a/src/android/net/apf/ApfV6Generator.java
+++ b/src/android/net/apf/ApfV6Generator.java
@@ -234,6 +234,34 @@
     }
 
     @Override
+    public ApfV6Generator addCountAndDropIfBytesAtOffsetEqualsAnyOf(int offset,
+            List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+            throws IllegalInstructionException {
+        return addLoadImmediate(R0, offset).addCountAndDropIfBytesAtR0EqualsAnyOf(bytesList, cnt);
+    }
+
+    @Override
+    public ApfV6Generator addCountAndPassIfBytesAtOffsetEqualsAnyOf(int offset,
+            List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+            throws IllegalInstructionException {
+        return addLoadImmediate(R0, offset).addCountAndPassIfBytesAtR0EqualsAnyOf(bytesList, cnt);
+    }
+
+    @Override
+    public ApfV6Generator addCountAndDropIfBytesAtOffsetEqualsNoneOf(int offset,
+            List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+            throws IllegalInstructionException {
+        return addLoadImmediate(R0, offset).addCountAndDropIfBytesAtR0EqualsNoneOf(bytesList, cnt);
+    }
+
+    @Override
+    public ApfV6Generator addCountAndPassIfBytesAtOffsetEqualsNoneOf(int offset,
+            List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+            throws IllegalInstructionException {
+        return addLoadImmediate(R0, offset).addCountAndPassIfBytesAtR0EqualsNoneOf(bytesList, cnt);
+    }
+
+    @Override
     public ApfV6Generator addCountAndDropIfR0IsNoneOf(@NonNull Set<Long> values,
             ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
         if (values.isEmpty()) {
@@ -259,4 +287,15 @@
         // Rbit1 means the extra be16 immediate is present
         return append(new Instruction(ExtendedOpcodes.ALLOCATE, Rbit1).addU16(size));
     }
+
+    @Override
+    public ApfV6Generator addTransmitWithoutChecksum() {
+        return addTransmit(-1 /* ipOfs */);
+    }
+
+    @Override
+    protected boolean handleOptimizedTransmit(int ipOfs, int csumOfs, int csumStart,
+                                              int partialCsum, boolean isUdp) {
+        return false;
+    }
 }
diff --git a/src/android/net/apf/ApfV6GeneratorBase.java b/src/android/net/apf/ApfV6GeneratorBase.java
index 00f05c0..68a29c5 100644
--- a/src/android/net/apf/ApfV6GeneratorBase.java
+++ b/src/android/net/apf/ApfV6GeneratorBase.java
@@ -150,9 +150,7 @@
      * Add an instruction to the end of the program to transmit the allocated buffer without
      * checksum.
      */
-    public final Type addTransmitWithoutChecksum() {
-        return addTransmit(-1 /* ipOfs */);
-    }
+    public abstract Type addTransmitWithoutChecksum();
 
     /**
      * Add an instruction to the end of the program to transmit the allocated buffer.
@@ -165,6 +163,8 @@
         return append(new Instruction(ExtendedOpcodes.TRANSMIT, Rbit0).addU8(ipOfs).addU8(255));
     }
 
+    protected abstract boolean handleOptimizedTransmit(int ipOfs, int csumOfs, int csumStart, int partialCsum, boolean isUdp);
+
     /**
      * Add an instruction to the end of the program to transmit the allocated buffer.
      */
@@ -178,6 +178,8 @@
             throw new IllegalArgumentException("L4 checksum requires csum offset of "
                                                + csumOfs + " < 255");
         }
+        if (handleOptimizedTransmit(ipOfs, csumOfs, csumStart, partialCsum, isUdp))
+            return self();
         return append(new Instruction(ExtendedOpcodes.TRANSMIT, isUdp ? Rbit1 : Rbit0)
                 .addU8(ipOfs).addU8(csumOfs).addU8(csumStart).addU16(partialCsum));
     }
@@ -394,6 +396,45 @@
             short tgt);
 
     /**
+     * Add an instruction to the end of the program to count and drop if the bytes of the
+     * packet at an offset specified by {@code offset} match any of the elements in
+     * {@code bytesList}.
+     * This method will use JBSPTRMATCH in APFv6.1 when possible.
+     */
+    public abstract Type addCountAndDropIfBytesAtOffsetEqualsAnyOf(int offset,
+            @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+            throws IllegalInstructionException;
+
+    /**
+     * Add an instruction to the end of the program to count and pass if the bytes of the
+     * packet at an offset specified by {@code offset} match any of the elements in
+     * {@code bytesList}.
+     * This method will use JBSPTRMATCH in APFv6.1 when possible.
+     */
+    public abstract Type addCountAndPassIfBytesAtOffsetEqualsAnyOf(int offset,
+            @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+            throws IllegalInstructionException;
+
+    /**
+     * Add an instruction to the end of the program to count and drop if the bytes of the
+     * packet at an offset specified by {@code offset} match none the elements in {@code bytesList}.
+     * This method will use JBSPTRMATCH in APFv6.1 when possible.
+     */
+    public abstract Type addCountAndDropIfBytesAtOffsetEqualsNoneOf(int offset,
+            @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+            throws IllegalInstructionException;
+
+    /**
+     * Add an instruction to the end of the program to count and pass if the bytes of the
+     * packet at an offset specified by {@code offset} match none of the elements in
+     * {@code bytesList}.
+     * This method will use JBSPTRMATCH in APFv6.1 when possible.
+     */
+    public abstract Type addCountAndPassIfBytesAtOffsetEqualsNoneOf(int offset,
+            @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
+            throws IllegalInstructionException;
+
+    /**
      * 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
      * equals {@code qtype}. Examines the payload starting at the offset in R0.
diff --git a/tests/unit/src/android/net/apf/ApfGeneratorTest.kt b/tests/unit/src/android/net/apf/ApfGeneratorTest.kt
index 03d360f..1c383bc 100644
--- a/tests/unit/src/android/net/apf/ApfGeneratorTest.kt
+++ b/tests/unit/src/android/net/apf/ApfGeneratorTest.kt
@@ -987,6 +987,95 @@
     }
 
     @Test
+    fun testJBSPTRMATCHOpcodeEncoding() {
+        assumeTrue(apfInterpreterVersion != ApfJniUtils.APF_INTERPRETER_VERSION_V6)
+        val dataBytes = HexDump.hexStringToByteArray(
+            "01020304050607080910111213141516171819202122232425262728293031323334353637383940"
+        )
+        val bytes1 = HexDump.hexStringToByteArray("0102")
+        val bytes2 = HexDump.hexStringToByteArray("0304")
+        val bytes3 = HexDump.hexStringToByteArray("0506")
+        val bytes4 = HexDump.hexStringToByteArray("0708")
+        val bytes5 = HexDump.hexStringToByteArray("0910")
+        val bytes6 = HexDump.hexStringToByteArray("1112")
+        val bytes7 = HexDump.hexStringToByteArray("1314")
+        val bytes8 = HexDump.hexStringToByteArray("1516")
+        val bytes9 = HexDump.hexStringToByteArray("1718")
+        val bytes10 = HexDump.hexStringToByteArray("1920")
+        val bytes11 = HexDump.hexStringToByteArray("2122")
+        val bytes12 = HexDump.hexStringToByteArray("2324")
+        val bytes13 = HexDump.hexStringToByteArray("2526")
+        val bytes14 = HexDump.hexStringToByteArray("2728")
+        val bytes15 = HexDump.hexStringToByteArray("2930")
+        val bytes16 = HexDump.hexStringToByteArray("3132")
+        val bytes17 = HexDump.hexStringToByteArray("3334")
+        val bytesAtOddIndex = HexDump.hexStringToByteArray("0203")
+        val notExistBytes = HexDump.hexStringToByteArray("ffff")
+        val total17BytesList = listOf(
+            bytes1,
+            bytes2,
+            bytes3,
+            bytes4,
+            bytes5,
+            bytes6,
+            bytes7,
+            bytes8,
+            bytes9,
+            bytes10,
+            bytes11,
+            bytes12,
+            bytes13,
+            bytes14,
+            bytes15,
+            bytes16,
+            bytes17,
+        )
+        val joinedBytes: ByteArray = total17BytesList.flatMap { it.toList() }.toByteArray()
+        var program = ApfV61Generator(apfInterpreterVersion, ramSize, clampSize)
+            .addPreloadData(dataBytes)
+            .addJumpIfBytesAtOffsetEqualsNoneOf(0, listOf(bytes1, notExistBytes), PASS_LABEL)
+            .addJumpIfBytesAtOffsetEqualsAnyOf(1, listOf(bytes1, bytes2), PASS_LABEL)
+            .addJumpIfBytesAtOffsetEqualsNoneOf(2, listOf(notExistBytes), PASS_LABEL)
+            .addJumpIfBytesAtOffsetEqualsAnyOf(3, total17BytesList, PASS_LABEL)
+            .addJumpIfBytesAtOffsetEqualsNoneOf(4, listOf(bytesAtOddIndex), PASS_LABEL)
+            .addJumpIfBytesAtOffsetEqualsNoneOf(6, listOf(joinedBytes), PASS_LABEL)
+            .generate()
+        var debugBufferSize = ramSize - program.size - Counter.totalSize()
+        assertContentEquals(listOf(
+            "0: data        40, ${HexDump.toHexString(dataBytes)}",
+            "43: debugbuf    size=$debugBufferSize",
+            "47: jbsptrne    pktofs=0, (2), PASS, @0[0102]",
+            "52: li          r0, 0",
+            "53: jbsne       r0, (2), PASS, ffff",
+            "58: jbsptreq    pktofs=1, (2), PASS, { @0[0102], @2[0304] }[2]",
+            "64: li          r0, 2",
+            "66: jbsne       r0, (2), PASS, ffff",
+            "71: jbsptreq    pktofs=3, (2), PASS, { @0[0102], @2[0304], @4[0506], @6[0708], " +
+                "@8[0910], @10[1112], @12[1314], @14[1516], @16[1718], @18[1920], @20[2122], " +
+                "@22[2324], @24[2526], @26[2728], @28[2930], @30[3132] }[16]",
+            "91: jbsptreq    pktofs=3, (2), PASS, @32[3334]",
+            "96: li          r0, 4",
+            "98: jbsne       r0, (2), PASS, 0203",
+            "103: li          r0, 6",
+            "105: jbsne       r0, (34), PASS, ${HexDump.toHexString(joinedBytes)}",
+        ), apfTestHelpers.disassembleApf(program).map{ it.trim() })
+
+        val largePrefix = ByteArray(510) { 0 }
+        program = ApfV61Generator(apfInterpreterVersion, ramSize, clampSize)
+            .addPreloadData(largePrefix + dataBytes)
+            .addJumpIfBytesAtOffsetEqualsAnyOf(1, listOf(bytes1, bytes2), PASS_LABEL)
+            .generate()
+        debugBufferSize = ramSize - program.size - Counter.totalSize()
+        assertContentEquals(listOf(
+            "0: data        550, ${HexDump.toHexString(largePrefix + dataBytes)}",
+            "553: debugbuf    size=$debugBufferSize",
+            "557: jbsptreq    pktofs=1, (2), PASS, @510[0102]",
+            "562: li          r0, 1",
+            "564: jbseq       r0, (2), PASS, 0304",
+        ), apfTestHelpers.disassembleApf(program).map{ it.trim() })
+    }
+
+    @Test
     fun testPassDrop() {
         var program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize)
                 .addDrop()