/* * Copyright (C) 2024 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 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 static android.net.apf.BaseApfGenerator.Register.R1; import android.annotation.NonNull; import com.android.net.module.util.HexDump; import java.nio.ByteBuffer; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; /** * The abstract class for APFv6 assembler/generator. * * @param the generator class * * @hide */ public abstract class ApfV6GeneratorBase> extends ApfV4GeneratorBase { final int mMaximumApfProgramSize; /** * Creates an ApfV6GeneratorBase instance which is able to emit instructions for the specified * {@code version} of the APF interpreter. Throws {@code IllegalInstructionException} if * the requested version is unsupported. * */ public ApfV6GeneratorBase(int maximumApfProgramSize) throws IllegalInstructionException { super(APF_VERSION_6, false); this.mMaximumApfProgramSize = maximumApfProgramSize; } /** * Add an instruction to the end of the program to increment the counter value and * immediately return PASS. * * @param cnt the counter number to be incremented. */ public final Type addCountAndPass(int cnt) { checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */, 1000 /* upperBound */); // PASS requires using Rbit0 because it shares opcode with DROP return append(new Instruction(Opcodes.PASSDROP, Rbit0).addUnsigned(cnt)); } /** * Add an instruction to the end of the program to let the program immediately return DROP. */ public final Type addDrop() { // DROP requires using Rbit1 because it shares opcode with PASS return append(new Instruction(Opcodes.PASSDROP, Rbit1)); } /** * Add an instruction to the end of the program to increment the counter value and * immediately return DROP. * * @param cnt the counter number to be incremented. */ public final Type addCountAndDrop(int cnt) { checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */, 1000 /* upperBound */); // DROP requires using Rbit1 because it shares opcode with PASS return append(new Instruction(Opcodes.PASSDROP, Rbit1).addUnsigned(cnt)); } /** * Add an instruction to the end of the program to call the apf_allocate_buffer() function. * Buffer length to be allocated is stored in register 0. */ public final Type addAllocateR0() { return append(new Instruction(ExtendedOpcodes.ALLOCATE)); } /** * Add an instruction to the end of the program to call the apf_allocate_buffer() function. * * @param size the buffer length to be allocated. */ public final Type addAllocate(int size) { // Rbit1 means the extra be16 immediate is present return append(new Instruction(ExtendedOpcodes.ALLOCATE, Rbit1).addU16(size)); } /** * Add an instruction to the beginning of the program to reserve the empty data region. */ public final Type addData() throws IllegalInstructionException { return addData(new byte[0]); } /** * Add an instruction to the beginning of the program to reserve the data region. * @param data the actual data byte */ public final Type addData(byte[] data) throws IllegalInstructionException { if (!mInstructions.isEmpty()) { throw new IllegalInstructionException("data instruction has to come first"); } if (data.length > 65535) { throw new IllegalArgumentException("data size larger than 65535"); } return append(new Instruction(Opcodes.JMP, Rbit1).addUnsigned(data.length) .setBytesImm(data).overrideImmSize(2)); } /** * Add an instruction to the end of the program to set the exception buffer size. * @param bufSize the exception buffer size */ public final Type addExceptionBuffer(int bufSize) throws IllegalInstructionException { return append(new Instruction(ExtendedOpcodes.EXCEPTIONBUFFER).addU16(bufSize)); } /** * Add an instruction to the end of the program to transmit the allocated buffer without * checksum. */ public final Type addTransmitWithoutChecksum() { return addTransmit(-1 /* ipOfs */); } /** * Add an instruction to the end of the program to transmit the allocated buffer. */ public final Type addTransmit(int ipOfs) { if (ipOfs >= 255) { throw new IllegalArgumentException("IP offset of " + ipOfs + " must be < 255"); } if (ipOfs == -1) ipOfs = 255; return append(new Instruction(ExtendedOpcodes.TRANSMIT, Rbit0).addU8(ipOfs).addU8(255)); } /** * Add an instruction to the end of the program to transmit the allocated buffer. */ public final Type addTransmitL4(int ipOfs, int csumOfs, int csumStart, int partialCsum, boolean isUdp) { if (ipOfs >= 255) { throw new IllegalArgumentException("IP offset of " + ipOfs + " must be < 255"); } if (ipOfs == -1) ipOfs = 255; if (csumOfs >= 255) { throw new IllegalArgumentException("L4 checksum requires csum offset of " + csumOfs + " < 255"); } return append(new Instruction(ExtendedOpcodes.TRANSMIT, isUdp ? Rbit1 : Rbit0) .addU8(ipOfs).addU8(csumOfs).addU8(csumStart).addU16(partialCsum)); } /** * Add an instruction to the end of the program to write 1 byte value to output buffer. */ public final Type addWriteU8(int val) { return append(new Instruction(Opcodes.WRITE).overrideImmSize(1).addU8(val)); } /** * Add an instruction to the end of the program to write 2 bytes value to output buffer. */ public final Type addWriteU16(int val) { return append(new Instruction(Opcodes.WRITE).overrideImmSize(2).addU16(val)); } /** * Add an instruction to the end of the program to write 4 bytes value to output buffer. */ public final Type addWriteU32(long val) { return append(new Instruction(Opcodes.WRITE).overrideImmSize(4).addU32(val)); } /** * Add an instruction to the end of the program to encode int value as 4 bytes to output buffer. */ public final Type addWrite32(int val) { return addWriteU32((long) val & 0xffffffffL); } /** * Add an instruction to the end of the program to write 4 bytes array to output buffer. */ public final Type addWrite32(@NonNull byte[] bytes) { Objects.requireNonNull(bytes); if (bytes.length != 4) { throw new IllegalArgumentException( "bytes array size must be 4, current size: " + bytes.length); } return addWrite32(((bytes[0] & 0xff) << 24) | ((bytes[1] & 0xff) << 16) | ((bytes[2] & 0xff) << 8) | (bytes[3] & 0xff)); } /** * Add an instruction to the end of the program to write 1 byte value from register to output * buffer. */ public final Type addWriteU8(Register reg) { return append(new Instruction(ExtendedOpcodes.EWRITE1, reg)); } /** * Add an instruction to the end of the program to write 2 byte value from register to output * buffer. */ public final Type addWriteU16(Register reg) { return append(new Instruction(ExtendedOpcodes.EWRITE2, reg)); } /** * Add an instruction to the end of the program to write 4 byte value from register to output * buffer. */ public final Type addWriteU32(Register reg) { return append(new Instruction(ExtendedOpcodes.EWRITE4, reg)); } /** * Add an instruction to the end of the program to copy data from APF program/data region to * output buffer and auto-increment the output buffer pointer. * This method requires the {@code addData} method to be called beforehand. * It will first attempt to match {@code content} with existing data bytes. If not exist, then * append the {@code content} to the data bytes. */ public final Type addDataCopy(@NonNull byte[] content) throws IllegalInstructionException { if (mInstructions.isEmpty()) { throw new IllegalInstructionException("There is no instructions"); } Objects.requireNonNull(content); int copySrc = mInstructions.get(0).maybeUpdateBytesImm(content); return addDataCopy(copySrc, content.length); } /** * Add an instruction to the end of the program to copy data from APF program/data region to * output buffer and auto-increment the output buffer pointer. * * @param src the offset inside the APF program/data region for where to start copy. * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at * one time. * @return the Type object */ public final Type addDataCopy(int src, int len) { return append(new Instruction(Opcodes.PKTDATACOPY, Rbit1).addDataOffset(src).addU8(len)); } /** * Add an instruction to the end of the program to copy data from input packet to output * buffer and auto-increment the output buffer pointer. * * @param src the offset inside the input packet for where to start copy. * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at * one time. * @return the Type object */ public final Type addPacketCopy(int src, int len) { return append(new Instruction(Opcodes.PKTDATACOPY, Rbit0).addPacketOffset(src).addU8(len)); } /** * Add an instruction to the end of the program to copy data from APF program/data region to * output buffer and auto-increment the output buffer pointer. * Source offset is stored in R0. * * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once. * @return the Type object */ public final Type addDataCopyFromR0(int len) { return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYIMM, Rbit1).addU8(len)); } /** * Add an instruction to the end of the program to copy data from input packet to output * buffer and auto-increment the output buffer pointer. * Source offset is stored in R0. * * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once. * @return the Type object */ public final Type addPacketCopyFromR0(int len) { return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYIMM, Rbit0).addU8(len)); } /** * Add an instruction to the end of the program to copy data from APF program/data region to * output buffer and auto-increment the output buffer pointer. * Source offset is stored in R0. * Copy length is stored in R1. * * @return the Type object */ public final Type addDataCopyFromR0LenR1() { return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYR1, Rbit1)); } /** * Add an instruction to the end of the program to copy data from input packet to output * buffer and auto-increment the output buffer pointer. * Source offset is stored in R0. * Copy length is stored in R1. * * @return the Type object */ public final Type addPacketCopyFromR0LenR1() { return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYR1, Rbit0)); } /** * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP * payload's DNS questions do NOT contain the QNAMEs specified in {@code qnames} and qtype * equals {@code qtype}. Examines the payload starting at the offset in R0. * R = 0 means check for "does not contain". * Drops packets if packets are corrupted. */ public final Type addJumpIfPktAtR0DoesNotContainDnsQ(@NonNull byte[] qnames, int qtype, @NonNull String tgt) { validateNames(qnames); return append(new Instruction(ExtendedOpcodes.JDNSQMATCH, Rbit0).setTargetLabel(tgt).addU8( qtype).setBytesImm(qnames)); } /** * Same as {@link #addJumpIfPktAtR0DoesNotContainDnsQ} except passes packets if packets are * corrupted. */ public final Type addJumpIfPktAtR0DoesNotContainDnsQSafe(@NonNull byte[] qnames, int qtype, @NonNull String tgt) { validateNames(qnames); return append(new Instruction(ExtendedOpcodes.JDNSQMATCHSAFE, Rbit0).setTargetLabel( tgt).addU8(qtype).setBytesImm(qnames)); } /** * 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. * R = 1 means check for "contain". * Drops packets if packets are corrupted. */ public final Type addJumpIfPktAtR0ContainDnsQ(@NonNull byte[] qnames, int qtype, @NonNull String tgt) { validateNames(qnames); return append(new Instruction(ExtendedOpcodes.JDNSQMATCH, Rbit1).setTargetLabel(tgt).addU8( qtype).setBytesImm(qnames)); } /** * Same as {@link #addJumpIfPktAtR0ContainDnsQ} except passes packets if packets are * corrupted. */ public final Type addJumpIfPktAtR0ContainDnsQSafe(@NonNull byte[] qnames, int qtype, @NonNull String tgt) { validateNames(qnames); return append(new Instruction(ExtendedOpcodes.JDNSQMATCHSAFE, Rbit1).setTargetLabel( tgt).addU8(qtype).setBytesImm(qnames)); } /** * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP * payload's DNS answers/authority/additional records do NOT contain the NAMEs * specified in {@code Names}. Examines the payload starting at the offset in R0. * R = 0 means check for "does not contain". * Drops packets if packets are corrupted. */ public final Type addJumpIfPktAtR0DoesNotContainDnsA(@NonNull byte[] names, @NonNull String tgt) { validateNames(names); return append(new Instruction(ExtendedOpcodes.JDNSAMATCH, Rbit0).setTargetLabel(tgt) .setBytesImm(names)); } /** * Same as {@link #addJumpIfPktAtR0DoesNotContainDnsA} except passes packets if packets are * corrupted. */ public final Type addJumpIfPktAtR0DoesNotContainDnsASafe(@NonNull byte[] names, @NonNull String tgt) { validateNames(names); return append(new Instruction(ExtendedOpcodes.JDNSAMATCHSAFE, Rbit0).setTargetLabel(tgt) .setBytesImm(names)); } /** * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP * payload's DNS answers/authority/additional records contain the NAMEs * specified in {@code Names}. Examines the payload starting at the offset in R0. * R = 1 means check for "contain". * Drops packets if packets are corrupted. */ public final Type addJumpIfPktAtR0ContainDnsA(@NonNull byte[] names, @NonNull String tgt) { validateNames(names); return append(new Instruction(ExtendedOpcodes.JDNSAMATCH, Rbit1).setTargetLabel( tgt).setBytesImm(names)); } /** * Same as {@link #addJumpIfPktAtR0ContainDnsA} except passes packets if packets are * corrupted. */ public final Type addJumpIfPktAtR0ContainDnsASafe(@NonNull byte[] names, @NonNull String tgt) { validateNames(names); return append(new Instruction(ExtendedOpcodes.JDNSAMATCHSAFE, Rbit1).setTargetLabel( tgt).setBytesImm(names)); } /** * 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 register0 match {@code bytes}. * R=1 means check for equal. */ public final Type addJumpIfBytesAtR0Equal(@NonNull byte[] bytes, String tgt) throws IllegalInstructionException { validateBytes(bytes); return append(new Instruction(Opcodes.JBSMATCH, R1).addUnsigned( bytes.length).setTargetLabel(tgt).setBytesImm(bytes)); } private Type addJumpIfBytesAtR0EqualsHelper(@NonNull List bytesList, String tgt, boolean jumpOnMatch) { final List deduplicatedList = validateDeduplicateBytesList(bytesList); final int elementSize = deduplicatedList.get(0).length; final int totalElements = deduplicatedList.size(); final int totalSize = elementSize * totalElements; final ByteBuffer buffer = ByteBuffer.allocate(totalSize); for (byte[] array : deduplicatedList) { buffer.put(array); } final Rbit rbit = jumpOnMatch ? Rbit1 : Rbit0; final byte[] combinedBytes = buffer.array(); return append(new Instruction(Opcodes.JBSMATCH, rbit) .addUnsigned((totalElements - 1) << 11 | elementSize) .setTargetLabel(tgt) .setBytesImm(combinedBytes)); } /** * 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 register0 match any of the elements in {@code bytesSet}. * R=1 means check for equal. */ public final Type addJumpIfBytesAtR0EqualsAnyOf(@NonNull List bytesList, String tgt) { return addJumpIfBytesAtR0EqualsHelper(bytesList, tgt, true /* 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 register0 match none of the elements in {@code bytesSet}. * R=0 means check for not equal. */ public final Type addJumpIfBytesAtR0EqualNoneOf(@NonNull List bytesList, String tgt) { return addJumpIfBytesAtR0EqualsHelper(bytesList, tgt, false /* jumpOnMatch */); } /** * Check if the byte is valid dns character: A-Z,0-9,-,_ */ private static boolean isValidDnsCharacter(byte c) { return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '%'; } private static void validateNames(@NonNull byte[] names) { final int len = names.length; if (len < 4) { throw new IllegalArgumentException("qnames must have at least length 4"); } final String errorMessage = "qname: " + HexDump.toHexString(names) + "is not null-terminated list of TLV-encoded names"; int i = 0; while (i < len - 1) { int label_len = names[i++]; // byte == 0xff means it is a '*' wildcard if (label_len == -1) continue; if (label_len < 1 || label_len > 63) { throw new IllegalArgumentException( "label len: " + label_len + " must be between 1 and 63"); } if (i + label_len >= len - 1) { throw new IllegalArgumentException(errorMessage); } while (label_len-- > 0) { if (!isValidDnsCharacter(names[i++])) { throw new IllegalArgumentException("qname: " + HexDump.toHexString(names) + " contains invalid character"); } } if (names[i] == 0) { i++; // skip null terminator. } } if (names[len - 1] != 0) { throw new IllegalArgumentException(errorMessage); } } private Type addJumpIfOneOfHelper(Register reg, @NonNull Set values, boolean jumpOnMatch, @NonNull String tgt) { if (values == null || values.size() < 2 || values.size() > 33) { throw new IllegalArgumentException( "size of values set must be >= 2 and <= 33, current size: " + values.size()); } final Long max = Collections.max(values); final Long min = Collections.min(values); checkRange("max value in set", max, 0, 4294967295L); checkRange("min value in set", min, 0, 4294967295L); // Since sets are always of size > 1 and in range [0, uint32_max], max is guaranteed > 0, // so maxImmSize can never be 0. final int maxImmSize = calculateImmSize(max.intValue(), false); // imm3(u8): top 5 bits - number of following u8/be16/be32 values - 2 // middle 2 bits - 1..4 length of immediates - 1 // bottom 1 bit - =0 jmp if in set, =1 if not in set Instruction instruction = new Instruction(ExtendedOpcodes.JONEOF, reg) .setTargetLabel(tgt) .addU8((values.size() - 2) << 3 | (maxImmSize - 1) << 1 | (jumpOnMatch ? 0 : 1)); for (Long v : values) { switch (maxImmSize) { case 1: instruction.addU8(v.intValue()); break; case 2: instruction.addU16(v.intValue()); break; // case 3: instruction.addU24(v); break; -- not supported by generator case 4: instruction.addU32(v); break; default: throw new IllegalArgumentException( "immLen is not in {1, 2, 4}, immLen: " + maxImmSize); } } return append(instruction); } /** * Add an instruction to the end of the program to jump to {@code tgt} if {@code reg} is * one of the {@code values}. */ public final Type addJumpIfOneOf(Register reg, @NonNull Set values, @NonNull String tgt) { return addJumpIfOneOfHelper(reg, values, true /* jumpOnMatch */, tgt); } /** * Add an instruction to the end of the program to jump to {@code tgt} if {@code reg} is * not one of the {@code values}. */ public final Type addJumpIfNoneOf(Register reg, @NonNull Set values, @NonNull String tgt) { return addJumpIfOneOfHelper(reg, values, false /* jumpOnMatch */, tgt); } @Override void addR0ArithR1(Opcodes opcode) { append(new Instruction(opcode, R0)); // APFv6+: R0 op= R1 } /** * Add an instruction to the end of the program to increment the counter value and * immediately return PASS. * * @param counter the counter enum to be incremented. */ @Override public final Type addCountAndPass(ApfCounterTracker.Counter counter) { checkPassCounterRange(counter); return addCountAndPass(counter.value()); } /** * Add an instruction to the end of the program to increment the counter value and * immediately return DROP. * * @param counter the counter enum to be incremented. */ @Override public final Type addCountAndDrop(ApfCounterTracker.Counter counter) { checkDropCounterRange(counter); return addCountAndDrop(counter.value()); } @Override public final Type addCountAndDropIfR0Equals(long val, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { final String tgt = getUniqueLabel(); return addJumpIfR0NotEquals(val, tgt).addCountAndDrop(cnt).defineLabel(tgt); } @Override public final Type addCountAndPassIfR0Equals(long val, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { final String tgt = getUniqueLabel(); return addJumpIfR0NotEquals(val, tgt).addCountAndPass(cnt).defineLabel(tgt); } @Override public final Type addCountAndDropIfR0NotEquals(long val, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { final String tgt = getUniqueLabel(); return addJumpIfR0Equals(val, tgt).addCountAndDrop(cnt).defineLabel(tgt); } @Override public final Type addCountAndPassIfR0NotEquals(long val, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { final String tgt = getUniqueLabel(); return addJumpIfR0Equals(val, tgt).addCountAndPass(cnt).defineLabel(tgt); } @Override public Type addCountAndDropIfR0AnyBitsSet(long val, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { final String countAndDropLabel = getUniqueLabel(); final String skipLabel = getUniqueLabel(); return addJumpIfR0AnyBitsSet(val, countAndDropLabel) .addJump(skipLabel) .defineLabel(countAndDropLabel) .addCountAndDrop(cnt) .defineLabel(skipLabel); } @Override public Type addCountAndPassIfR0AnyBitsSet(long val, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { final String countAndPassLabel = getUniqueLabel(); final String skipLabel = getUniqueLabel(); return addJumpIfR0AnyBitsSet(val, countAndPassLabel) .addJump(skipLabel) .defineLabel(countAndPassLabel) .addCountAndPass(cnt) .defineLabel(skipLabel); } @Override public final Type addCountAndDropIfR0LessThan(long val, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { if (val <= 0) { throw new IllegalArgumentException("val must > 0, current val: " + val); } final String tgt = getUniqueLabel(); return addJumpIfR0GreaterThan(val - 1, tgt).addCountAndDrop(cnt).defineLabel(tgt); } @Override public final Type addCountAndPassIfR0LessThan(long val, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { if (val <= 0) { throw new IllegalArgumentException("val must > 0, current val: " + val); } final String tgt = getUniqueLabel(); return addJumpIfR0GreaterThan(val - 1, tgt).addCountAndPass(cnt).defineLabel(tgt); } @Override public Type addCountAndDropIfR0GreaterThan(long val, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { if (val < 0 || val >= 4294967295L) { throw new IllegalArgumentException("val must >= 0 and < 2^32-1, current val: " + val); } final String tgt = getUniqueLabel(); return addJumpIfR0LessThan(val + 1, tgt).addCountAndDrop(cnt).defineLabel(tgt); } @Override public Type addCountAndPassIfR0GreaterThan(long val, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { if (val < 0 || val >= 4294967295L) { throw new IllegalArgumentException("val must >= 0 and < 2^32-1, current val: " + val); } final String tgt = getUniqueLabel(); return addJumpIfR0LessThan(val + 1, tgt).addCountAndPass(cnt).defineLabel(tgt); } @Override public final Type addCountAndDropIfBytesAtR0NotEqual(byte[] bytes, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { final String tgt = getUniqueLabel(); return addJumpIfBytesAtR0Equal(bytes, tgt).addCountAndDrop(cnt).defineLabel(tgt); } @Override public final Type addCountAndPassIfBytesAtR0NotEqual(byte[] bytes, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { final String tgt = getUniqueLabel(); return addJumpIfBytesAtR0Equal(bytes, tgt).addCountAndPass(cnt).defineLabel(tgt); } @Override public final Type addCountAndDropIfBytesAtR0Equal(byte[] bytes, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { final String tgt = getUniqueLabel(); return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndDrop(cnt).defineLabel(tgt); } @Override public final Type addCountAndPassIfBytesAtR0Equal(byte[] bytes, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { final String tgt = getUniqueLabel(); return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndPass(cnt).defineLabel(tgt); } @Override public Type addCountAndPassIfR0IsOneOf(@NonNull Set values, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { if (values.isEmpty()) { throw new IllegalArgumentException("values cannot be empty"); } if (values.size() == 1) { return addCountAndPassIfR0Equals(values.iterator().next(), cnt); } final String tgt = getUniqueLabel(); return addJumpIfNoneOf(R0, values, tgt).addCountAndPass(cnt).defineLabel(tgt); } @Override public Type addCountAndDropIfR0IsOneOf(@NonNull Set values, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { if (values.isEmpty()) { throw new IllegalArgumentException("values cannot be empty"); } if (values.size() == 1) { return addCountAndDropIfR0Equals(values.iterator().next(), cnt); } final String tgt = getUniqueLabel(); return addJumpIfNoneOf(R0, values, tgt).addCountAndDrop(cnt).defineLabel(tgt); } @Override public Type addCountAndPassIfR0IsNoneOf(@NonNull Set values, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { if (values.isEmpty()) { throw new IllegalArgumentException("values cannot be empty"); } if (values.size() == 1) { return addCountAndPassIfR0NotEquals(values.iterator().next(), cnt); } final String tgt = getUniqueLabel(); return addJumpIfOneOf(R0, values, tgt).addCountAndPass(cnt).defineLabel(tgt); } @Override public Type addCountAndDropIfBytesAtR0EqualsAnyOf(@NonNull List bytesList, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { final String tgt = getUniqueLabel(); return addJumpIfBytesAtR0EqualNoneOf(bytesList, tgt).addCountAndDrop(cnt).defineLabel(tgt); } @Override public Type addCountAndPassIfBytesAtR0EqualsAnyOf(@NonNull List bytesList, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { final String tgt = getUniqueLabel(); return addJumpIfBytesAtR0EqualNoneOf(bytesList, tgt).addCountAndPass(cnt).defineLabel(tgt); } @Override public Type addCountAndDropIfBytesAtR0EqualsNoneOf(@NonNull List bytesList, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { final String tgt = getUniqueLabel(); return addJumpIfBytesAtR0EqualsAnyOf(bytesList, tgt).addCountAndDrop(cnt).defineLabel(tgt); } @Override public Type addCountAndPassIfBytesAtR0EqualsNoneOf(@NonNull List bytesList, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { final String tgt = getUniqueLabel(); return addJumpIfBytesAtR0EqualsAnyOf(bytesList, tgt).addCountAndPass(cnt).defineLabel(tgt); } @Override public Type addCountAndDropIfR0IsNoneOf(@NonNull Set values, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { if (values.isEmpty()) { throw new IllegalArgumentException("values cannot be empty"); } if (values.size() == 1) { return addCountAndDropIfR0NotEquals(values.iterator().next(), cnt); } final String tgt = getUniqueLabel(); return addJumpIfOneOf(R0, values, tgt).addCountAndDrop(cnt).defineLabel(tgt); } @Override public final Type addLoadCounter(Register register, ApfCounterTracker.Counter counter) throws IllegalInstructionException { return append(new Instruction(Opcodes.LDDW, register).addUnsigned(counter.value())); } @Override public final Type addStoreCounter(ApfCounterTracker.Counter counter, Register register) throws IllegalInstructionException { return append(new Instruction(Opcodes.STDW, register).addUnsigned(counter.value())); } /** * This method is noop in APFv6. */ @Override public final Type addCountTrampoline() { return self(); } }