blob: b8359ece5d57e91d918499edf176d1468c4cdadb [file] [log] [blame]
/*
* 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 androidx.annotation.NonNull;
import com.android.net.module.util.HexDump;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* The abstract class for APFv6 assembler/generator.
*
* @param <Type> the generator class
*
* @hide
*/
public abstract class ApfV6GeneratorBase<Type extends ApfV6GeneratorBase<Type>> extends
ApfV4GeneratorBase<Type> {
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 List<byte[]> validateDeduplicateBytesList(List<byte[]> bytesList) {
if (bytesList == null || bytesList.size() == 0) {
throw new IllegalArgumentException(
"bytesList size must > 0, current size: "
+ (bytesList == null ? "null" : bytesList.size()));
}
for (byte[] bytes : bytesList) {
validateBytes(bytes);
}
final int elementSize = bytesList.get(0).length;
if (elementSize > 2097151) { // 2 ^ 21 - 1
throw new IllegalArgumentException("too many elements");
}
List<byte[]> deduplicatedList = new ArrayList<>();
deduplicatedList.add(bytesList.get(0));
for (int i = 1; i < bytesList.size(); ++i) {
if (elementSize != bytesList.get(i).length) {
throw new IllegalArgumentException("byte arrays in the set have different size");
}
int j = 0;
for (; j < deduplicatedList.size(); ++j) {
if (Arrays.equals(bytesList.get(i), deduplicatedList.get(j))) {
break;
}
}
if (j == deduplicatedList.size()) {
deduplicatedList.add(bytesList.get(i));
}
}
return deduplicatedList;
}
private Type addJumpIfBytesAtR0EqualsHelper(@NonNull List<byte[]> bytesList, String tgt,
boolean jumpOnMatch) {
final List<byte[]> 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<byte[]> 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<byte[]> 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<Long> 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<Long> 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<Long> 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 {
checkDropCounterRange(cnt);
final String tgt = getUniqueLabel();
return addJumpIfR0NotEquals(val, tgt).addCountAndDrop(cnt).defineLabel(tgt);
}
@Override
public final Type addCountAndPassIfR0Equals(long val, ApfCounterTracker.Counter cnt)
throws IllegalInstructionException {
checkPassCounterRange(cnt);
final String tgt = getUniqueLabel();
return addJumpIfR0NotEquals(val, tgt).addCountAndPass(cnt).defineLabel(tgt);
}
@Override
public final Type addCountAndDropIfR0NotEquals(long val, ApfCounterTracker.Counter cnt)
throws IllegalInstructionException {
checkDropCounterRange(cnt);
final String tgt = getUniqueLabel();
return addJumpIfR0Equals(val, tgt).addCountAndDrop(cnt).defineLabel(tgt);
}
@Override
public final Type addCountAndPassIfR0NotEquals(long val, ApfCounterTracker.Counter cnt)
throws IllegalInstructionException {
checkPassCounterRange(cnt);
final String tgt = getUniqueLabel();
return addJumpIfR0Equals(val, tgt).addCountAndPass(cnt).defineLabel(tgt);
}
@Override
public final Type addCountAndDropIfR0LessThan(long val, ApfCounterTracker.Counter cnt)
throws IllegalInstructionException {
checkDropCounterRange(cnt);
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 {
checkPassCounterRange(cnt);
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 final Type addCountAndDropIfBytesAtR0NotEqual(byte[] bytes,
ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
checkDropCounterRange(cnt);
final String tgt = getUniqueLabel();
return addJumpIfBytesAtR0Equal(bytes, tgt).addCountAndDrop(cnt).defineLabel(tgt);
}
@Override
public final Type addCountAndPassIfBytesAtR0NotEqual(byte[] bytes,
ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
checkPassCounterRange(cnt);
final String tgt = getUniqueLabel();
return addJumpIfBytesAtR0Equal(bytes, tgt).addCountAndPass(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();
}
}