1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.net.apf;
17 
18 import static android.net.apf.BaseApfGenerator.Rbit.Rbit0;
19 import static android.net.apf.BaseApfGenerator.Rbit.Rbit1;
20 import static android.net.apf.BaseApfGenerator.Register.R0;
21 import static android.net.apf.BaseApfGenerator.Register.R1;
22 
23 import android.annotation.NonNull;
24 
25 import com.android.net.module.util.HexDump;
26 
27 import java.nio.ByteBuffer;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Objects;
31 import java.util.Set;
32 
33 /**
34  * The abstract class for APFv6 assembler/generator.
35  *
36  * @param <Type> the generator class
37  *
38  * @hide
39  */
40 public abstract class ApfV6GeneratorBase<Type extends ApfV6GeneratorBase<Type>> extends
41         ApfV4GeneratorBase<Type> {
42 
43     final int mMaximumApfProgramSize;
44 
45     /**
46      * Creates an ApfV6GeneratorBase instance which is able to emit instructions for the specified
47      * {@code version} of the APF interpreter. Throws {@code IllegalInstructionException} if
48      * the requested version is unsupported.
49      *
50      */
ApfV6GeneratorBase(int maximumApfProgramSize)51     public ApfV6GeneratorBase(int maximumApfProgramSize) throws IllegalInstructionException {
52         super(APF_VERSION_6, false);
53         this.mMaximumApfProgramSize = maximumApfProgramSize;
54     }
55 
56     /**
57      * Add an instruction to the end of the program to increment the counter value and
58      * immediately return PASS.
59      *
60      * @param cnt the counter number to be incremented.
61      */
addCountAndPass(int cnt)62     public final Type addCountAndPass(int cnt) {
63         checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */,
64                 1000 /* upperBound */);
65         // PASS requires using Rbit0 because it shares opcode with DROP
66         return append(new Instruction(Opcodes.PASSDROP, Rbit0).addUnsigned(cnt));
67     }
68 
69     /**
70      * Add an instruction to the end of the program to let the program immediately return DROP.
71      */
addDrop()72     public final Type addDrop() {
73         // DROP requires using Rbit1 because it shares opcode with PASS
74         return append(new Instruction(Opcodes.PASSDROP, Rbit1));
75     }
76 
77     /**
78      * Add an instruction to the end of the program to increment the counter value and
79      * immediately return DROP.
80      *
81      * @param cnt the counter number to be incremented.
82      */
addCountAndDrop(int cnt)83     public final Type addCountAndDrop(int cnt) {
84         checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */,
85                 1000 /* upperBound */);
86         // DROP requires using Rbit1 because it shares opcode with PASS
87         return append(new Instruction(Opcodes.PASSDROP, Rbit1).addUnsigned(cnt));
88     }
89 
90     /**
91      * Add an instruction to the end of the program to call the apf_allocate_buffer() function.
92      * Buffer length to be allocated is stored in register 0.
93      */
addAllocateR0()94     public final Type addAllocateR0() {
95         return append(new Instruction(ExtendedOpcodes.ALLOCATE));
96     }
97 
98     /**
99      * Add an instruction to the end of the program to call the apf_allocate_buffer() function.
100      *
101      * @param size the buffer length to be allocated.
102      */
addAllocate(int size)103     public final Type addAllocate(int size) {
104         // Rbit1 means the extra be16 immediate is present
105         return append(new Instruction(ExtendedOpcodes.ALLOCATE, Rbit1).addU16(size));
106     }
107 
108     /**
109      * Add an instruction to the beginning of the program to reserve the empty data region.
110      */
addData()111     public final Type addData() throws IllegalInstructionException {
112         return addData(new byte[0]);
113     }
114 
115     /**
116      * Add an instruction to the beginning of the program to reserve the data region.
117      * @param data the actual data byte
118      */
addData(byte[] data)119     public final Type addData(byte[] data) throws IllegalInstructionException {
120         if (!mInstructions.isEmpty()) {
121             throw new IllegalInstructionException("data instruction has to come first");
122         }
123         if (data.length > 65535) {
124             throw new IllegalArgumentException("data size larger than 65535");
125         }
126         return append(new Instruction(Opcodes.JMP, Rbit1).addUnsigned(data.length)
127                 .setBytesImm(data).overrideImmSize(2));
128     }
129 
130     /**
131      * Add an instruction to the end of the program to set the exception buffer size.
132      * @param bufSize the exception buffer size
133      */
addExceptionBuffer(int bufSize)134     public final Type addExceptionBuffer(int bufSize) throws IllegalInstructionException {
135         return append(new Instruction(ExtendedOpcodes.EXCEPTIONBUFFER).addU16(bufSize));
136     }
137 
138     /**
139      * Add an instruction to the end of the program to transmit the allocated buffer without
140      * checksum.
141      */
addTransmitWithoutChecksum()142     public final Type addTransmitWithoutChecksum() {
143         return addTransmit(-1 /* ipOfs */);
144     }
145 
146     /**
147      * Add an instruction to the end of the program to transmit the allocated buffer.
148      */
addTransmit(int ipOfs)149     public final Type addTransmit(int ipOfs) {
150         if (ipOfs >= 255) {
151             throw new IllegalArgumentException("IP offset of " + ipOfs + " must be < 255");
152         }
153         if (ipOfs == -1) ipOfs = 255;
154         return append(new Instruction(ExtendedOpcodes.TRANSMIT, Rbit0).addU8(ipOfs).addU8(255));
155     }
156 
157     /**
158      * Add an instruction to the end of the program to transmit the allocated buffer.
159      */
addTransmitL4(int ipOfs, int csumOfs, int csumStart, int partialCsum, boolean isUdp)160     public final Type addTransmitL4(int ipOfs, int csumOfs, int csumStart, int partialCsum,
161                                         boolean isUdp) {
162         if (ipOfs >= 255) {
163             throw new IllegalArgumentException("IP offset of " + ipOfs + " must be < 255");
164         }
165         if (ipOfs == -1) ipOfs = 255;
166         if (csumOfs >= 255) {
167             throw new IllegalArgumentException("L4 checksum requires csum offset of "
168                                                + csumOfs + " < 255");
169         }
170         return append(new Instruction(ExtendedOpcodes.TRANSMIT, isUdp ? Rbit1 : Rbit0)
171                 .addU8(ipOfs).addU8(csumOfs).addU8(csumStart).addU16(partialCsum));
172     }
173 
174     /**
175      * Add an instruction to the end of the program to write 1 byte value to output buffer.
176      */
addWriteU8(int val)177     public final Type addWriteU8(int val) {
178         return append(new Instruction(Opcodes.WRITE).overrideImmSize(1).addU8(val));
179     }
180 
181     /**
182      * Add an instruction to the end of the program to write 2 bytes value to output buffer.
183      */
addWriteU16(int val)184     public final Type addWriteU16(int val) {
185         return append(new Instruction(Opcodes.WRITE).overrideImmSize(2).addU16(val));
186     }
187 
188     /**
189      * Add an instruction to the end of the program to write 4 bytes value to output buffer.
190      */
addWriteU32(long val)191     public final Type addWriteU32(long val) {
192         return append(new Instruction(Opcodes.WRITE).overrideImmSize(4).addU32(val));
193     }
194 
195     /**
196      * Add an instruction to the end of the program to encode int value as 4 bytes to output buffer.
197      */
addWrite32(int val)198     public final Type addWrite32(int val) {
199         return addWriteU32((long) val & 0xffffffffL);
200     }
201 
202     /**
203      * Add an instruction to the end of the program to write 4 bytes array to output buffer.
204      */
addWrite32(@onNull byte[] bytes)205     public final Type addWrite32(@NonNull byte[] bytes) {
206         Objects.requireNonNull(bytes);
207         if (bytes.length != 4) {
208             throw new IllegalArgumentException(
209                     "bytes array size must be 4, current size: " + bytes.length);
210         }
211         return addWrite32(((bytes[0] & 0xff) << 24)
212                 | ((bytes[1] & 0xff) << 16)
213                 | ((bytes[2] & 0xff) << 8)
214                 | (bytes[3] & 0xff));
215     }
216 
217     /**
218      * Add an instruction to the end of the program to write 1 byte value from register to output
219      * buffer.
220      */
addWriteU8(Register reg)221     public final Type addWriteU8(Register reg) {
222         return append(new Instruction(ExtendedOpcodes.EWRITE1, reg));
223     }
224 
225     /**
226      * Add an instruction to the end of the program to write 2 byte value from register to output
227      * buffer.
228      */
addWriteU16(Register reg)229     public final Type addWriteU16(Register reg) {
230         return append(new Instruction(ExtendedOpcodes.EWRITE2, reg));
231     }
232 
233     /**
234      * Add an instruction to the end of the program to write 4 byte value from register to output
235      * buffer.
236      */
addWriteU32(Register reg)237     public final Type addWriteU32(Register reg) {
238         return append(new Instruction(ExtendedOpcodes.EWRITE4, reg));
239     }
240 
241     /**
242      * Add an instruction to the end of the program to copy data from APF program/data region to
243      * output buffer and auto-increment the output buffer pointer.
244      * This method requires the {@code addData} method to be called beforehand.
245      * It will first attempt to match {@code content} with existing data bytes. If not exist, then
246      * append the {@code content} to the data bytes.
247      */
addDataCopy(@onNull byte[] content)248     public final Type addDataCopy(@NonNull byte[] content) throws IllegalInstructionException {
249         if (mInstructions.isEmpty()) {
250             throw new IllegalInstructionException("There is no instructions");
251         }
252         Objects.requireNonNull(content);
253         int copySrc = mInstructions.get(0).maybeUpdateBytesImm(content);
254         return addDataCopy(copySrc, content.length);
255     }
256 
257     /**
258      * Add an instruction to the end of the program to copy data from APF program/data region to
259      * output buffer and auto-increment the output buffer pointer.
260      *
261      * @param src the offset inside the APF program/data region for where to start copy.
262      * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at
263      *               one time.
264      * @return the Type object
265      */
addDataCopy(int src, int len)266     public final Type addDataCopy(int src, int len) {
267         return append(new Instruction(Opcodes.PKTDATACOPY, Rbit1).addDataOffset(src).addU8(len));
268     }
269 
270     /**
271      * Add an instruction to the end of the program to copy data from input packet to output
272      * buffer and auto-increment the output buffer pointer.
273      *
274      * @param src the offset inside the input packet for where to start copy.
275      * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at
276      *               one time.
277      * @return the Type object
278      */
addPacketCopy(int src, int len)279     public final Type addPacketCopy(int src, int len) {
280         return append(new Instruction(Opcodes.PKTDATACOPY, Rbit0).addPacketOffset(src).addU8(len));
281     }
282 
283     /**
284      * Add an instruction to the end of the program to copy data from APF program/data region to
285      * output buffer and auto-increment the output buffer pointer.
286      * Source offset is stored in R0.
287      *
288      * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once.
289      * @return the Type object
290      */
addDataCopyFromR0(int len)291     public final Type addDataCopyFromR0(int len) {
292         return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYIMM, Rbit1).addU8(len));
293     }
294 
295     /**
296      * Add an instruction to the end of the program to copy data from input packet to output
297      * buffer and auto-increment the output buffer pointer.
298      * Source offset is stored in R0.
299      *
300      * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once.
301      * @return the Type object
302      */
addPacketCopyFromR0(int len)303     public final Type addPacketCopyFromR0(int len) {
304         return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYIMM, Rbit0).addU8(len));
305     }
306 
307     /**
308      * Add an instruction to the end of the program to copy data from APF program/data region to
309      * output buffer and auto-increment the output buffer pointer.
310      * Source offset is stored in R0.
311      * Copy length is stored in R1.
312      *
313      * @return the Type object
314      */
addDataCopyFromR0LenR1()315     public final Type addDataCopyFromR0LenR1() {
316         return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYR1, Rbit1));
317     }
318 
319     /**
320      * Add an instruction to the end of the program to copy data from input packet to output
321      * buffer and auto-increment the output buffer pointer.
322      * Source offset is stored in R0.
323      * Copy length is stored in R1.
324      *
325      * @return the Type object
326      */
addPacketCopyFromR0LenR1()327     public final Type addPacketCopyFromR0LenR1() {
328         return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYR1, Rbit0));
329     }
330 
331     /**
332      * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
333      * payload's DNS questions do NOT contain the QNAMEs specified in {@code qnames} and qtype
334      * equals {@code qtype}. Examines the payload starting at the offset in R0.
335      * R = 0 means check for "does not contain".
336      * Drops packets if packets are corrupted.
337      */
addJumpIfPktAtR0DoesNotContainDnsQ(@onNull byte[] qnames, int qtype, @NonNull String tgt)338     public final Type addJumpIfPktAtR0DoesNotContainDnsQ(@NonNull byte[] qnames, int qtype,
339                                                              @NonNull String tgt) {
340         validateNames(qnames);
341         return append(new Instruction(ExtendedOpcodes.JDNSQMATCH, Rbit0).setTargetLabel(tgt).addU8(
342                 qtype).setBytesImm(qnames));
343     }
344 
345     /**
346      * Same as {@link #addJumpIfPktAtR0DoesNotContainDnsQ} except passes packets if packets are
347      * corrupted.
348      */
addJumpIfPktAtR0DoesNotContainDnsQSafe(@onNull byte[] qnames, int qtype, @NonNull String tgt)349     public final Type addJumpIfPktAtR0DoesNotContainDnsQSafe(@NonNull byte[] qnames, int qtype,
350             @NonNull String tgt) {
351         validateNames(qnames);
352         return append(new Instruction(ExtendedOpcodes.JDNSQMATCHSAFE, Rbit0).setTargetLabel(
353                 tgt).addU8(qtype).setBytesImm(qnames));
354     }
355 
356     /**
357      * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
358      * payload's DNS questions contain the QNAMEs specified in {@code qnames} and qtype
359      * equals {@code qtype}. Examines the payload starting at the offset in R0.
360      * R = 1 means check for "contain".
361      * Drops packets if packets are corrupted.
362      */
addJumpIfPktAtR0ContainDnsQ(@onNull byte[] qnames, int qtype, @NonNull String tgt)363     public final Type addJumpIfPktAtR0ContainDnsQ(@NonNull byte[] qnames, int qtype,
364                                                       @NonNull String tgt) {
365         validateNames(qnames);
366         return append(new Instruction(ExtendedOpcodes.JDNSQMATCH, Rbit1).setTargetLabel(tgt).addU8(
367                 qtype).setBytesImm(qnames));
368     }
369 
370     /**
371      * Same as {@link #addJumpIfPktAtR0ContainDnsQ} except passes packets if packets are
372      * corrupted.
373      */
addJumpIfPktAtR0ContainDnsQSafe(@onNull byte[] qnames, int qtype, @NonNull String tgt)374     public final Type addJumpIfPktAtR0ContainDnsQSafe(@NonNull byte[] qnames, int qtype,
375             @NonNull String tgt) {
376         validateNames(qnames);
377         return append(new Instruction(ExtendedOpcodes.JDNSQMATCHSAFE, Rbit1).setTargetLabel(
378                 tgt).addU8(qtype).setBytesImm(qnames));
379     }
380 
381     /**
382      * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
383      * payload's DNS answers/authority/additional records do NOT contain the NAMEs
384      * specified in {@code Names}. Examines the payload starting at the offset in R0.
385      * R = 0 means check for "does not contain".
386      * Drops packets if packets are corrupted.
387      */
addJumpIfPktAtR0DoesNotContainDnsA(@onNull byte[] names, @NonNull String tgt)388     public final Type addJumpIfPktAtR0DoesNotContainDnsA(@NonNull byte[] names,
389                                                              @NonNull String tgt) {
390         validateNames(names);
391         return append(new Instruction(ExtendedOpcodes.JDNSAMATCH, Rbit0).setTargetLabel(tgt)
392                         .setBytesImm(names));
393     }
394 
395     /**
396      * Same as {@link #addJumpIfPktAtR0DoesNotContainDnsA} except passes packets if packets are
397      * corrupted.
398      */
addJumpIfPktAtR0DoesNotContainDnsASafe(@onNull byte[] names, @NonNull String tgt)399     public final Type addJumpIfPktAtR0DoesNotContainDnsASafe(@NonNull byte[] names,
400             @NonNull String tgt) {
401         validateNames(names);
402         return append(new Instruction(ExtendedOpcodes.JDNSAMATCHSAFE, Rbit0).setTargetLabel(tgt)
403                 .setBytesImm(names));
404     }
405 
406     /**
407      * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
408      * payload's DNS answers/authority/additional records contain the NAMEs
409      * specified in {@code Names}. Examines the payload starting at the offset in R0.
410      * R = 1 means check for "contain".
411      * Drops packets if packets are corrupted.
412      */
addJumpIfPktAtR0ContainDnsA(@onNull byte[] names, @NonNull String tgt)413     public final Type addJumpIfPktAtR0ContainDnsA(@NonNull byte[] names,
414                                                       @NonNull String tgt) {
415         validateNames(names);
416         return append(new Instruction(ExtendedOpcodes.JDNSAMATCH, Rbit1).setTargetLabel(
417                 tgt).setBytesImm(names));
418     }
419 
420     /**
421      * Same as {@link #addJumpIfPktAtR0ContainDnsA} except passes packets if packets are
422      * corrupted.
423      */
addJumpIfPktAtR0ContainDnsASafe(@onNull byte[] names, @NonNull String tgt)424     public final Type addJumpIfPktAtR0ContainDnsASafe(@NonNull byte[] names,
425             @NonNull String tgt) {
426         validateNames(names);
427         return append(new Instruction(ExtendedOpcodes.JDNSAMATCHSAFE, Rbit1).setTargetLabel(
428                 tgt).setBytesImm(names));
429     }
430 
431     /**
432      * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
433      * packet at an offset specified by register0 match {@code bytes}.
434      * R=1 means check for equal.
435      */
addJumpIfBytesAtR0Equal(@onNull byte[] bytes, String tgt)436     public final Type addJumpIfBytesAtR0Equal(@NonNull byte[] bytes, String tgt)
437             throws IllegalInstructionException {
438         validateBytes(bytes);
439         return append(new Instruction(Opcodes.JBSMATCH, R1).addUnsigned(
440                 bytes.length).setTargetLabel(tgt).setBytesImm(bytes));
441     }
442 
addJumpIfBytesAtR0EqualsHelper(@onNull List<byte[]> bytesList, String tgt, boolean jumpOnMatch)443     private Type addJumpIfBytesAtR0EqualsHelper(@NonNull List<byte[]> bytesList, String tgt,
444             boolean jumpOnMatch) {
445         final List<byte[]> deduplicatedList = validateDeduplicateBytesList(bytesList);
446         final int elementSize = deduplicatedList.get(0).length;
447         final int totalElements = deduplicatedList.size();
448         final int totalSize = elementSize * totalElements;
449         final ByteBuffer buffer = ByteBuffer.allocate(totalSize);
450         for (byte[] array : deduplicatedList) {
451             buffer.put(array);
452         }
453         final Rbit rbit = jumpOnMatch ? Rbit1 : Rbit0;
454         final byte[] combinedBytes = buffer.array();
455         return append(new Instruction(Opcodes.JBSMATCH, rbit)
456                 .addUnsigned((totalElements - 1) << 11 | elementSize)
457                 .setTargetLabel(tgt)
458                 .setBytesImm(combinedBytes));
459     }
460 
461     /**
462      * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
463      * packet at an offset specified by register0 match any of the elements in {@code bytesSet}.
464      * R=1 means check for equal.
465      */
addJumpIfBytesAtR0EqualsAnyOf(@onNull List<byte[]> bytesList, String tgt)466     public final Type addJumpIfBytesAtR0EqualsAnyOf(@NonNull List<byte[]> bytesList, String tgt) {
467         return addJumpIfBytesAtR0EqualsHelper(bytesList, tgt, true /* jumpOnMatch */);
468     }
469 
470     /**
471      * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
472      * packet at an offset specified by register0 match none of the elements in {@code bytesSet}.
473      * R=0 means check for not equal.
474      */
addJumpIfBytesAtR0EqualNoneOf(@onNull List<byte[]> bytesList, String tgt)475     public final Type addJumpIfBytesAtR0EqualNoneOf(@NonNull List<byte[]> bytesList, String tgt) {
476         return addJumpIfBytesAtR0EqualsHelper(bytesList, tgt, false /* jumpOnMatch */);
477     }
478 
479 
480     /**
481      * Check if the byte is valid dns character: A-Z,0-9,-,_
482      */
isValidDnsCharacter(byte c)483     private static boolean isValidDnsCharacter(byte c) {
484         return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '%';
485     }
486 
validateNames(@onNull byte[] names)487     private static void validateNames(@NonNull byte[] names) {
488         final int len = names.length;
489         if (len < 4) {
490             throw new IllegalArgumentException("qnames must have at least length 4");
491         }
492         final String errorMessage = "qname: " + HexDump.toHexString(names)
493                 + "is not null-terminated list of TLV-encoded names";
494         int i = 0;
495         while (i < len - 1) {
496             int label_len = names[i++];
497             // byte == 0xff means it is a '*' wildcard
498             if (label_len == -1) continue;
499             if (label_len < 1 || label_len > 63) {
500                 throw new IllegalArgumentException(
501                         "label len: " + label_len + " must be between 1 and 63");
502             }
503             if (i + label_len >= len - 1) {
504                 throw new IllegalArgumentException(errorMessage);
505             }
506             while (label_len-- > 0) {
507                 if (!isValidDnsCharacter(names[i++])) {
508                     throw new IllegalArgumentException("qname: " + HexDump.toHexString(names)
509                             + " contains invalid character");
510                 }
511             }
512             if (names[i] == 0) {
513                 i++; // skip null terminator.
514             }
515         }
516         if (names[len - 1] != 0) {
517             throw new IllegalArgumentException(errorMessage);
518         }
519     }
520 
addJumpIfOneOfHelper(Register reg, @NonNull Set<Long> values, boolean jumpOnMatch, @NonNull String tgt)521     private Type addJumpIfOneOfHelper(Register reg, @NonNull Set<Long> values,
522             boolean jumpOnMatch, @NonNull String tgt) {
523         if (values == null || values.size() < 2 || values.size() > 33)  {
524             throw new IllegalArgumentException(
525                     "size of values set must be >= 2 and <= 33, current size: " + values.size());
526         }
527         final Long max = Collections.max(values);
528         final Long min = Collections.min(values);
529         checkRange("max value in set", max, 0, 4294967295L);
530         checkRange("min value in set", min, 0, 4294967295L);
531         // Since sets are always of size > 1 and in range [0, uint32_max], max is guaranteed > 0,
532         // so maxImmSize can never be 0.
533         final int maxImmSize = calculateImmSize(max.intValue(), false);
534 
535         // imm3(u8): top 5 bits - number of following u8/be16/be32 values - 2
536         // middle 2 bits - 1..4 length of immediates - 1
537         // bottom 1 bit - =0 jmp if in set, =1 if not in set
538         Instruction instruction = new Instruction(ExtendedOpcodes.JONEOF, reg)
539                 .setTargetLabel(tgt)
540                 .addU8((values.size() - 2) << 3 | (maxImmSize - 1) << 1 | (jumpOnMatch ? 0 : 1));
541         for (Long v : values) {
542             switch (maxImmSize) {
543                 case 1:
544                     instruction.addU8(v.intValue());
545                     break;
546                 case 2:
547                     instruction.addU16(v.intValue());
548                     break;
549                 // case 3: instruction.addU24(v); break; -- not supported by generator
550                 case 4:
551                     instruction.addU32(v);
552                     break;
553                 default:
554                     throw new IllegalArgumentException(
555                             "immLen is not in {1, 2, 4}, immLen: " + maxImmSize);
556             }
557         }
558         return append(instruction);
559     }
560 
561     /**
562      * Add an instruction to the end of the program to jump to {@code tgt} if {@code reg} is
563      * one of the {@code values}.
564      */
addJumpIfOneOf(Register reg, @NonNull Set<Long> values, @NonNull String tgt)565     public final Type addJumpIfOneOf(Register reg, @NonNull Set<Long> values,
566             @NonNull String tgt) {
567         return addJumpIfOneOfHelper(reg, values, true /* jumpOnMatch */, tgt);
568     }
569 
570     /**
571      * Add an instruction to the end of the program to jump to {@code tgt} if {@code reg} is
572      * not one of the {@code values}.
573      */
addJumpIfNoneOf(Register reg, @NonNull Set<Long> values, @NonNull String tgt)574     public final Type addJumpIfNoneOf(Register reg, @NonNull Set<Long> values,
575             @NonNull String tgt) {
576         return addJumpIfOneOfHelper(reg, values, false /* jumpOnMatch */, tgt);
577     }
578 
579     @Override
addR0ArithR1(Opcodes opcode)580     void addR0ArithR1(Opcodes opcode) {
581         append(new Instruction(opcode, R0));  // APFv6+: R0 op= R1
582     }
583 
584     /**
585      * Add an instruction to the end of the program to increment the counter value and
586      * immediately return PASS.
587      *
588      * @param counter the counter enum to be incremented.
589      */
590     @Override
addCountAndPass(ApfCounterTracker.Counter counter)591     public final Type addCountAndPass(ApfCounterTracker.Counter counter) {
592         checkPassCounterRange(counter);
593         return addCountAndPass(counter.value());
594     }
595 
596     /**
597      * Add an instruction to the end of the program to increment the counter value and
598      * immediately return DROP.
599      *
600      * @param counter the counter enum to be incremented.
601      */
602     @Override
addCountAndDrop(ApfCounterTracker.Counter counter)603     public final Type addCountAndDrop(ApfCounterTracker.Counter counter) {
604         checkDropCounterRange(counter);
605         return addCountAndDrop(counter.value());
606     }
607 
608     @Override
addCountAndDropIfR0Equals(long val, ApfCounterTracker.Counter cnt)609     public final Type addCountAndDropIfR0Equals(long val, ApfCounterTracker.Counter cnt)
610             throws IllegalInstructionException {
611         final String tgt = getUniqueLabel();
612         return addJumpIfR0NotEquals(val, tgt).addCountAndDrop(cnt).defineLabel(tgt);
613     }
614 
615     @Override
addCountAndPassIfR0Equals(long val, ApfCounterTracker.Counter cnt)616     public final Type addCountAndPassIfR0Equals(long val, ApfCounterTracker.Counter cnt)
617             throws IllegalInstructionException {
618         final String tgt = getUniqueLabel();
619         return addJumpIfR0NotEquals(val, tgt).addCountAndPass(cnt).defineLabel(tgt);
620     }
621 
622     @Override
addCountAndDropIfR0NotEquals(long val, ApfCounterTracker.Counter cnt)623     public final Type addCountAndDropIfR0NotEquals(long val, ApfCounterTracker.Counter cnt)
624             throws IllegalInstructionException {
625         final String tgt = getUniqueLabel();
626         return addJumpIfR0Equals(val, tgt).addCountAndDrop(cnt).defineLabel(tgt);
627     }
628 
629     @Override
addCountAndPassIfR0NotEquals(long val, ApfCounterTracker.Counter cnt)630     public final Type addCountAndPassIfR0NotEquals(long val, ApfCounterTracker.Counter cnt)
631             throws IllegalInstructionException {
632         final String tgt = getUniqueLabel();
633         return addJumpIfR0Equals(val, tgt).addCountAndPass(cnt).defineLabel(tgt);
634     }
635 
636     @Override
addCountAndDropIfR0AnyBitsSet(long val, ApfCounterTracker.Counter cnt)637     public Type addCountAndDropIfR0AnyBitsSet(long val, ApfCounterTracker.Counter cnt)
638             throws IllegalInstructionException {
639         final String countAndDropLabel = getUniqueLabel();
640         final String skipLabel = getUniqueLabel();
641         return addJumpIfR0AnyBitsSet(val, countAndDropLabel)
642                 .addJump(skipLabel)
643                 .defineLabel(countAndDropLabel)
644                 .addCountAndDrop(cnt)
645                 .defineLabel(skipLabel);
646     }
647 
648     @Override
addCountAndPassIfR0AnyBitsSet(long val, ApfCounterTracker.Counter cnt)649     public Type addCountAndPassIfR0AnyBitsSet(long val, ApfCounterTracker.Counter cnt)
650             throws IllegalInstructionException {
651         final String countAndPassLabel = getUniqueLabel();
652         final String skipLabel = getUniqueLabel();
653         return addJumpIfR0AnyBitsSet(val, countAndPassLabel)
654                 .addJump(skipLabel)
655                 .defineLabel(countAndPassLabel)
656                 .addCountAndPass(cnt)
657                 .defineLabel(skipLabel);
658     }
659 
660     @Override
addCountAndDropIfR0LessThan(long val, ApfCounterTracker.Counter cnt)661     public final Type addCountAndDropIfR0LessThan(long val, ApfCounterTracker.Counter cnt)
662             throws IllegalInstructionException {
663         if (val <= 0) {
664             throw new IllegalArgumentException("val must > 0, current val: " + val);
665         }
666         final String tgt = getUniqueLabel();
667         return addJumpIfR0GreaterThan(val - 1, tgt).addCountAndDrop(cnt).defineLabel(tgt);
668     }
669 
670     @Override
addCountAndPassIfR0LessThan(long val, ApfCounterTracker.Counter cnt)671     public final Type addCountAndPassIfR0LessThan(long val, ApfCounterTracker.Counter cnt)
672             throws IllegalInstructionException {
673         if (val <= 0) {
674             throw new IllegalArgumentException("val must > 0, current val: " + val);
675         }
676         final String tgt = getUniqueLabel();
677         return addJumpIfR0GreaterThan(val - 1, tgt).addCountAndPass(cnt).defineLabel(tgt);
678     }
679 
680     @Override
addCountAndDropIfR0GreaterThan(long val, ApfCounterTracker.Counter cnt)681     public Type addCountAndDropIfR0GreaterThan(long val, ApfCounterTracker.Counter cnt)
682             throws IllegalInstructionException {
683         if (val < 0 || val >= 4294967295L) {
684             throw new IllegalArgumentException("val must >= 0 and < 2^32-1, current val: " + val);
685         }
686         final String tgt = getUniqueLabel();
687         return addJumpIfR0LessThan(val + 1, tgt).addCountAndDrop(cnt).defineLabel(tgt);
688     }
689 
690     @Override
addCountAndPassIfR0GreaterThan(long val, ApfCounterTracker.Counter cnt)691     public Type addCountAndPassIfR0GreaterThan(long val, ApfCounterTracker.Counter cnt)
692             throws IllegalInstructionException {
693         if (val < 0 || val >= 4294967295L) {
694             throw new IllegalArgumentException("val must >= 0 and < 2^32-1, current val: " + val);
695         }
696         final String tgt = getUniqueLabel();
697         return addJumpIfR0LessThan(val + 1, tgt).addCountAndPass(cnt).defineLabel(tgt);
698     }
699 
700     @Override
addCountAndDropIfBytesAtR0NotEqual(byte[] bytes, ApfCounterTracker.Counter cnt)701     public final Type addCountAndDropIfBytesAtR0NotEqual(byte[] bytes,
702             ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
703         final String tgt = getUniqueLabel();
704         return addJumpIfBytesAtR0Equal(bytes, tgt).addCountAndDrop(cnt).defineLabel(tgt);
705     }
706 
707     @Override
addCountAndPassIfBytesAtR0NotEqual(byte[] bytes, ApfCounterTracker.Counter cnt)708     public final Type addCountAndPassIfBytesAtR0NotEqual(byte[] bytes,
709             ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
710         final String tgt = getUniqueLabel();
711         return addJumpIfBytesAtR0Equal(bytes, tgt).addCountAndPass(cnt).defineLabel(tgt);
712     }
713 
714     @Override
addCountAndDropIfBytesAtR0Equal(byte[] bytes, ApfCounterTracker.Counter cnt)715     public final Type addCountAndDropIfBytesAtR0Equal(byte[] bytes,
716             ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
717         final String tgt = getUniqueLabel();
718         return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndDrop(cnt).defineLabel(tgt);
719     }
720 
721     @Override
addCountAndPassIfBytesAtR0Equal(byte[] bytes, ApfCounterTracker.Counter cnt)722     public final Type addCountAndPassIfBytesAtR0Equal(byte[] bytes,
723             ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
724         final String tgt = getUniqueLabel();
725         return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndPass(cnt).defineLabel(tgt);
726     }
727 
728     @Override
addCountAndPassIfR0IsOneOf(@onNull Set<Long> values, ApfCounterTracker.Counter cnt)729     public Type addCountAndPassIfR0IsOneOf(@NonNull Set<Long> values,
730             ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
731         if (values.isEmpty()) {
732             throw new IllegalArgumentException("values cannot be empty");
733         }
734         if (values.size() == 1) {
735             return addCountAndPassIfR0Equals(values.iterator().next(), cnt);
736         }
737         final String tgt = getUniqueLabel();
738         return addJumpIfNoneOf(R0, values, tgt).addCountAndPass(cnt).defineLabel(tgt);
739     }
740 
741     @Override
addCountAndDropIfR0IsOneOf(@onNull Set<Long> values, ApfCounterTracker.Counter cnt)742     public Type addCountAndDropIfR0IsOneOf(@NonNull Set<Long> values,
743             ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
744         if (values.isEmpty()) {
745             throw new IllegalArgumentException("values cannot be empty");
746         }
747         if (values.size() == 1) {
748             return addCountAndDropIfR0Equals(values.iterator().next(), cnt);
749         }
750         final String tgt = getUniqueLabel();
751         return addJumpIfNoneOf(R0, values, tgt).addCountAndDrop(cnt).defineLabel(tgt);
752     }
753 
754     @Override
addCountAndPassIfR0IsNoneOf(@onNull Set<Long> values, ApfCounterTracker.Counter cnt)755     public Type addCountAndPassIfR0IsNoneOf(@NonNull Set<Long> values,
756             ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
757         if (values.isEmpty()) {
758             throw new IllegalArgumentException("values cannot be empty");
759         }
760         if (values.size() == 1) {
761             return addCountAndPassIfR0NotEquals(values.iterator().next(), cnt);
762         }
763         final String tgt = getUniqueLabel();
764         return addJumpIfOneOf(R0, values, tgt).addCountAndPass(cnt).defineLabel(tgt);
765     }
766 
767     @Override
addCountAndDropIfBytesAtR0EqualsAnyOf(@onNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)768     public Type addCountAndDropIfBytesAtR0EqualsAnyOf(@NonNull List<byte[]> bytesList,
769             ApfCounterTracker.Counter cnt)
770             throws IllegalInstructionException {
771         final String tgt = getUniqueLabel();
772         return addJumpIfBytesAtR0EqualNoneOf(bytesList, tgt).addCountAndDrop(cnt).defineLabel(tgt);
773     }
774 
775     @Override
addCountAndPassIfBytesAtR0EqualsAnyOf(@onNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)776     public Type addCountAndPassIfBytesAtR0EqualsAnyOf(@NonNull List<byte[]> bytesList,
777             ApfCounterTracker.Counter cnt)
778             throws IllegalInstructionException {
779         final String tgt = getUniqueLabel();
780         return addJumpIfBytesAtR0EqualNoneOf(bytesList, tgt).addCountAndPass(cnt).defineLabel(tgt);
781     }
782 
783     @Override
addCountAndDropIfBytesAtR0EqualsNoneOf(@onNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)784     public Type addCountAndDropIfBytesAtR0EqualsNoneOf(@NonNull List<byte[]> bytesList,
785             ApfCounterTracker.Counter cnt)
786             throws IllegalInstructionException {
787         final String tgt = getUniqueLabel();
788         return addJumpIfBytesAtR0EqualsAnyOf(bytesList, tgt).addCountAndDrop(cnt).defineLabel(tgt);
789     }
790 
791     @Override
addCountAndPassIfBytesAtR0EqualsNoneOf(@onNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)792     public Type addCountAndPassIfBytesAtR0EqualsNoneOf(@NonNull List<byte[]> bytesList,
793             ApfCounterTracker.Counter cnt)
794             throws IllegalInstructionException {
795         final String tgt = getUniqueLabel();
796         return addJumpIfBytesAtR0EqualsAnyOf(bytesList, tgt).addCountAndPass(cnt).defineLabel(tgt);
797     }
798 
799     @Override
addCountAndDropIfR0IsNoneOf(@onNull Set<Long> values, ApfCounterTracker.Counter cnt)800     public Type addCountAndDropIfR0IsNoneOf(@NonNull Set<Long> values,
801             ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
802         if (values.isEmpty()) {
803             throw new IllegalArgumentException("values cannot be empty");
804         }
805         if (values.size() == 1) {
806             return addCountAndDropIfR0NotEquals(values.iterator().next(), cnt);
807         }
808         final String tgt = getUniqueLabel();
809         return addJumpIfOneOf(R0, values, tgt).addCountAndDrop(cnt).defineLabel(tgt);
810     }
811 
812     @Override
addLoadCounter(Register register, ApfCounterTracker.Counter counter)813     public final Type addLoadCounter(Register register, ApfCounterTracker.Counter counter)
814             throws IllegalInstructionException {
815         return append(new Instruction(Opcodes.LDDW, register).addUnsigned(counter.value()));
816     }
817 
818     @Override
addStoreCounter(ApfCounterTracker.Counter counter, Register register)819     public final Type addStoreCounter(ApfCounterTracker.Counter counter, Register register)
820             throws IllegalInstructionException {
821         return append(new Instruction(Opcodes.STDW, register).addUnsigned(counter.value()));
822     }
823 
824     /**
825      * This method is noop in APFv6.
826      */
827     @Override
addCountTrampoline()828     public final Type addCountTrampoline() {
829         return self();
830     }
831 }
832