• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     /**
44      * Creates an ApfV6GeneratorBase instance which is able to emit instructions for the specified
45      * {@code version} of the APF interpreter. Throws {@code IllegalInstructionException} if
46      * the requested version is unsupported.
47      *
48      */
ApfV6GeneratorBase(byte[] bytes, int version, int ramSize, int clampSize)49     public ApfV6GeneratorBase(byte[] bytes, int version, int ramSize, int clampSize)
50             throws IllegalInstructionException {
51         super(version, ramSize, clampSize, false);
52         Objects.requireNonNull(bytes);
53         addData(bytes);
54         addExceptionBuffer(0);
55     }
56 
57     @Override
getBaseProgramSize()58     public final int getBaseProgramSize() {
59         // When the APFv6+ generator is initialized, it always adds a 3-byte data jump
60         // instruction and a 4-byte exception instruction to the front of the program.
61         return 7;
62     }
63 
64     @Override
updateExceptionBufferSize(int programSize)65     void updateExceptionBufferSize(int programSize) throws IllegalInstructionException {
66         mInstructions.get(1).updateExceptionBufferSize(
67                 mRamSize - ApfCounterTracker.Counter.totalSize() - programSize);
68     }
69 
70     /**
71      * Add an instruction to the end of the program to increment the counter value and
72      * immediately return PASS.
73      *
74      * @param cnt the counter number to be incremented.
75      */
addCountAndPass(int cnt)76     public final Type addCountAndPass(int cnt) {
77         checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */,
78                 1000 /* upperBound */);
79         // PASS requires using Rbit0 because it shares opcode with DROP
80         return append(new Instruction(Opcodes.PASSDROP, Rbit0).addUnsigned(cnt));
81     }
82 
83     /**
84      * Add an instruction to the end of the program to let the program immediately return DROP.
85      */
addDrop()86     public final Type addDrop() {
87         // DROP requires using Rbit1 because it shares opcode with PASS
88         return append(new Instruction(Opcodes.PASSDROP, Rbit1));
89     }
90 
91     /**
92      * Add an instruction to the end of the program to increment the counter value and
93      * immediately return DROP.
94      *
95      * @param cnt the counter number to be incremented.
96      */
addCountAndDrop(int cnt)97     public final Type addCountAndDrop(int cnt) {
98         checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */,
99                 1000 /* upperBound */);
100         // DROP requires using Rbit1 because it shares opcode with PASS
101         return append(new Instruction(Opcodes.PASSDROP, Rbit1).addUnsigned(cnt));
102     }
103 
104     /**
105      * Add an instruction to the end of the program to call the apf_allocate_buffer() function.
106      * Buffer length to be allocated is stored in register 0.
107      */
addAllocateR0()108     public final Type addAllocateR0() {
109         return append(new Instruction(ExtendedOpcodes.ALLOCATE));
110     }
111 
112     /**
113      * Add an instruction to the end of the program to call the apf_allocate_buffer() function.
114      *
115      * @param size the buffer length to be allocated.
116      */
addAllocate(int size)117     public abstract Type addAllocate(int size);
118 
119     /**
120      * Add an instruction to the beginning of the program to reserve the empty data region.
121      */
addData()122     public final Type addData() throws IllegalInstructionException {
123         return addData(new byte[0]);
124     }
125 
126     /**
127      * Add an instruction to the beginning of the program to reserve the data region.
128      * @param data the actual data byte
129      */
addData(byte[] data)130     public final Type addData(byte[] data) throws IllegalInstructionException {
131         if (!mInstructions.isEmpty()) {
132             throw new IllegalInstructionException("data instruction has to come first");
133         }
134         if (data.length > 65535) {
135             throw new IllegalArgumentException("data size larger than 65535");
136         }
137         return append(new Instruction(Opcodes.JMP, Rbit1).addUnsigned(data.length)
138                 .setBytesImm(data).overrideImmSize(2));
139     }
140 
141     /**
142      * Add an instruction to the end of the program to set the exception buffer size.
143      * @param bufSize the exception buffer size
144      */
addExceptionBuffer(int bufSize)145     public final Type addExceptionBuffer(int bufSize) throws IllegalInstructionException {
146         return append(new Instruction(ExtendedOpcodes.EXCEPTIONBUFFER).addU16(bufSize));
147     }
148 
149     /**
150      * Add an instruction to the end of the program to transmit the allocated buffer without
151      * checksum.
152      */
addTransmitWithoutChecksum()153     public abstract Type addTransmitWithoutChecksum();
154 
155     /**
156      * Add an instruction to the end of the program to transmit the allocated buffer.
157      */
addTransmit(int ipOfs)158     public final Type addTransmit(int ipOfs) {
159         if (ipOfs >= 255) {
160             throw new IllegalArgumentException("IP offset of " + ipOfs + " must be < 255");
161         }
162         if (ipOfs == -1) ipOfs = 255;
163         return append(new Instruction(ExtendedOpcodes.TRANSMIT, Rbit0).addU8(ipOfs).addU8(255));
164     }
165 
handleOptimizedTransmit(int ipOfs, int csumOfs, int csumStart, int partialCsum, boolean isUdp)166     protected abstract boolean handleOptimizedTransmit(int ipOfs, int csumOfs, int csumStart, int partialCsum, boolean isUdp);
167 
168     /**
169      * Add an instruction to the end of the program to transmit the allocated buffer.
170      */
addTransmitL4(int ipOfs, int csumOfs, int csumStart, int partialCsum, boolean isUdp)171     public final Type addTransmitL4(int ipOfs, int csumOfs, int csumStart, int partialCsum,
172                                         boolean isUdp) {
173         if (ipOfs >= 255) {
174             throw new IllegalArgumentException("IP offset of " + ipOfs + " must be < 255");
175         }
176         if (ipOfs == -1) ipOfs = 255;
177         if (csumOfs >= 255) {
178             throw new IllegalArgumentException("L4 checksum requires csum offset of "
179                                                + csumOfs + " < 255");
180         }
181         if (handleOptimizedTransmit(ipOfs, csumOfs, csumStart, partialCsum, isUdp))
182             return self();
183         return append(new Instruction(ExtendedOpcodes.TRANSMIT, isUdp ? Rbit1 : Rbit0)
184                 .addU8(ipOfs).addU8(csumOfs).addU8(csumStart).addU16(partialCsum));
185     }
186 
187     /**
188      * Add an instruction to the end of the program to write 1 byte value to output buffer.
189      */
addWriteU8(int val)190     public final Type addWriteU8(int val) {
191         return append(new Instruction(Opcodes.WRITE).overrideImmSize(1).addU8(val));
192     }
193 
194     /**
195      * Add an instruction to the end of the program to write 2 bytes value to output buffer.
196      */
addWriteU16(int val)197     public final Type addWriteU16(int val) {
198         return append(new Instruction(Opcodes.WRITE).overrideImmSize(2).addU16(val));
199     }
200 
201     /**
202      * Add an instruction to the end of the program to write 4 bytes value to output buffer.
203      */
addWriteU32(long val)204     public final Type addWriteU32(long val) {
205         return append(new Instruction(Opcodes.WRITE).overrideImmSize(4).addU32(val));
206     }
207 
208     /**
209      * Add an instruction to the end of the program to encode int value as 4 bytes to output buffer.
210      */
addWrite32(int val)211     public final Type addWrite32(int val) {
212         return addWriteU32((long) val & 0xffffffffL);
213     }
214 
215     /**
216      * Add an instruction to the end of the program to write 4 bytes array to output buffer.
217      */
addWrite32(@onNull byte[] bytes)218     public final Type addWrite32(@NonNull byte[] bytes) {
219         Objects.requireNonNull(bytes);
220         if (bytes.length != 4) {
221             throw new IllegalArgumentException(
222                     "bytes array size must be 4, current size: " + bytes.length);
223         }
224         return addWrite32(((bytes[0] & 0xff) << 24)
225                 | ((bytes[1] & 0xff) << 16)
226                 | ((bytes[2] & 0xff) << 8)
227                 | (bytes[3] & 0xff));
228     }
229 
230     /**
231      * Add an instruction to the end of the program to write 1 byte value from register to output
232      * buffer.
233      */
addWriteU8(Register reg)234     public final Type addWriteU8(Register reg) {
235         return append(new Instruction(ExtendedOpcodes.EWRITE1, reg));
236     }
237 
238     /**
239      * Add an instruction to the end of the program to write 2 byte value from register to output
240      * buffer.
241      */
addWriteU16(Register reg)242     public final Type addWriteU16(Register reg) {
243         return append(new Instruction(ExtendedOpcodes.EWRITE2, reg));
244     }
245 
246     /**
247      * Add an instruction to the end of the program to write 4 byte value from register to output
248      * buffer.
249      */
addWriteU32(Register reg)250     public final Type addWriteU32(Register reg) {
251         return append(new Instruction(ExtendedOpcodes.EWRITE4, reg));
252     }
253 
254     /**
255      * Add instructions to the end of the program to copy data from APF program/data region to
256      * output buffer and auto-increment the output buffer pointer.
257      * This method requires the {@code addData} method to be called beforehand.
258      * It will first attempt to match {@code content} with existing data bytes. If not exist, then
259      * append the {@code content} to the data bytes.
260      * The method copies the content using multiple datacopy instructions if the content size
261      * exceeds 255 bytes. Each instruction will copy a maximum of 255 bytes.
262      */
addDataCopy(@onNull byte[] content)263     public final Type addDataCopy(@NonNull byte[] content) throws IllegalInstructionException {
264         if (mInstructions.isEmpty()) {
265             throw new IllegalInstructionException("There is no instructions");
266         }
267         Objects.requireNonNull(content);
268         final int chunkSize = 255;
269         for (int fromIndex = 0; fromIndex < content.length; fromIndex += chunkSize) {
270             final int toIndex = Math.min(content.length, fromIndex + chunkSize);
271             final int copySrc = mInstructions.get(0).maybeUpdateBytesImm(content, fromIndex,
272                     toIndex);
273             addDataCopy(copySrc, (toIndex - fromIndex));
274         }
275         return self();
276     }
277 
278     /**
279      * Add the content to the data region if it wasn't exist.
280      */
maybeUpdateDataRegion(@onNull byte[] content)281     public final Type maybeUpdateDataRegion(@NonNull byte[] content)
282             throws IllegalInstructionException {
283         if (mInstructions.isEmpty()) {
284             throw new IllegalInstructionException("There are no instructions");
285         }
286         mInstructions.get(0).maybeUpdateBytesImm(content, 0, content.length);
287         return self();
288     }
289 
290     /**
291      * Add an instruction to the end of the program to copy data from APF program/data region to
292      * output buffer and auto-increment the output buffer pointer.
293      *
294      * @param src the offset inside the APF program/data region for where to start copy.
295      * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at
296      *               one time.
297      * @return the Type object
298      */
addDataCopy(int src, int len)299     public final Type addDataCopy(int src, int len) {
300         return append(new Instruction(Opcodes.PKTDATACOPY, Rbit1).addDataOffset(src).addU8(len));
301     }
302 
303     /**
304      * Add an instruction to the end of the program to copy data from input packet to output
305      * buffer and auto-increment the output buffer pointer.
306      *
307      * @param src the offset inside the input packet for where to start copy.
308      * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at
309      *               one time.
310      * @return the Type object
311      */
addPacketCopy(int src, int len)312     public final Type addPacketCopy(int src, int len) {
313         return append(new Instruction(Opcodes.PKTDATACOPY, Rbit0).addPacketOffset(src).addU8(len));
314     }
315 
316     /**
317      * Add an instruction to the end of the program to copy data from APF program/data region to
318      * output buffer and auto-increment the output buffer pointer.
319      * Source offset is stored in R0.
320      *
321      * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once.
322      * @return the Type object
323      */
addDataCopyFromR0(int len)324     public final Type addDataCopyFromR0(int len) {
325         return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYIMM, Rbit1).addU8(len));
326     }
327 
328     /**
329      * Add an instruction to the end of the program to copy data from input packet to output
330      * buffer and auto-increment the output buffer pointer.
331      * Source offset is stored in R0.
332      *
333      * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once.
334      * @return the Type object
335      */
addPacketCopyFromR0(int len)336     public final Type addPacketCopyFromR0(int len) {
337         return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYIMM, Rbit0).addU8(len));
338     }
339 
340     /**
341      * Add an instruction to the end of the program to copy data from APF program/data region to
342      * output buffer and auto-increment the output buffer pointer.
343      * Source offset is stored in R0.
344      * Copy length is stored in R1.
345      *
346      * @return the Type object
347      */
addDataCopyFromR0LenR1()348     public final Type addDataCopyFromR0LenR1() {
349         return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYR1, Rbit1));
350     }
351 
352     /**
353      * Add an instruction to the end of the program to copy data from input packet to output
354      * buffer and auto-increment the output buffer pointer.
355      * Source offset is stored in R0.
356      * Copy length is stored in R1.
357      *
358      * @return the Type object
359      */
addPacketCopyFromR0LenR1()360     public final Type addPacketCopyFromR0LenR1() {
361         return append(new Instruction(ExtendedOpcodes.EPKTDATACOPYR1, Rbit0));
362     }
363 
364     /**
365      * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
366      * payload's DNS questions do NOT contain the QNAMEs specified in {@code qnames} and qtype
367      * equals {@code qtype}. Examines the payload starting at the offset in R0.
368      * R = 0 means check for "does not contain".
369      * Drops packets if packets are corrupted.
370      */
addJumpIfPktAtR0DoesNotContainDnsQ(@onNull byte[] qnames, int qtype, short tgt)371     public final Type addJumpIfPktAtR0DoesNotContainDnsQ(@NonNull byte[] qnames, int qtype,
372             short tgt) {
373         validateNames(qnames);
374         return append(new Instruction(ExtendedOpcodes.JDNSQMATCH, Rbit0).setTargetLabel(tgt).addU8(
375                 qtype).setBytesImm(qnames));
376     }
377 
378     /**
379      * Same as {@link #addJumpIfPktAtR0DoesNotContainDnsQ} except passes packets if packets are
380      * corrupted.
381      */
addJumpIfPktAtR0DoesNotContainDnsQSafe(@onNull byte[] qnames, int qtype, short tgt)382     public final Type addJumpIfPktAtR0DoesNotContainDnsQSafe(@NonNull byte[] qnames, int qtype,
383             short tgt) {
384         validateNames(qnames);
385         return append(new Instruction(ExtendedOpcodes.JDNSQMATCHSAFE, Rbit0).setTargetLabel(
386                 tgt).addU8(qtype).setBytesImm(qnames));
387     }
388 
389     /**
390      * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
391      * payload's DNS questions contain the QNAMEs specified in {@code qnames} and qtype
392      * equals any of {@code qtypes}. Examines the payload starting at the offset in R0.
393      * Drops packets if packets are corrupted.
394      */
addJumpIfPktAtR0ContainDnsQ(@onNull byte[] qnames, @NonNull int[] qtypes, short tgt)395     public abstract Type addJumpIfPktAtR0ContainDnsQ(@NonNull byte[] qnames, @NonNull int[] qtypes,
396             short tgt);
397 
398     /**
399      * Add an instruction to the end of the program to count and drop if the bytes of the
400      * packet at an offset specified by {@code offset} match any of the elements in
401      * {@code bytesList}.
402      * This method will use JBSPTRMATCH in APFv6.1 when possible.
403      */
addCountAndDropIfBytesAtOffsetEqualsAnyOf(int offset, @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)404     public abstract Type addCountAndDropIfBytesAtOffsetEqualsAnyOf(int offset,
405             @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
406             throws IllegalInstructionException;
407 
408     /**
409      * Add an instruction to the end of the program to count and pass if the bytes of the
410      * packet at an offset specified by {@code offset} match any of the elements in
411      * {@code bytesList}.
412      * This method will use JBSPTRMATCH in APFv6.1 when possible.
413      */
addCountAndPassIfBytesAtOffsetEqualsAnyOf(int offset, @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)414     public abstract Type addCountAndPassIfBytesAtOffsetEqualsAnyOf(int offset,
415             @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
416             throws IllegalInstructionException;
417 
418     /**
419      * Add an instruction to the end of the program to count and drop if the bytes of the
420      * packet at an offset specified by {@code offset} match none the elements in {@code bytesList}.
421      * This method will use JBSPTRMATCH in APFv6.1 when possible.
422      */
addCountAndDropIfBytesAtOffsetEqualsNoneOf(int offset, @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)423     public abstract Type addCountAndDropIfBytesAtOffsetEqualsNoneOf(int offset,
424             @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
425             throws IllegalInstructionException;
426 
427     /**
428      * Add an instruction to the end of the program to count and pass if the bytes of the
429      * packet at an offset specified by {@code offset} match none of the elements in
430      * {@code bytesList}.
431      * This method will use JBSPTRMATCH in APFv6.1 when possible.
432      */
addCountAndPassIfBytesAtOffsetEqualsNoneOf(int offset, @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)433     public abstract Type addCountAndPassIfBytesAtOffsetEqualsNoneOf(int offset,
434             @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt)
435             throws IllegalInstructionException;
436 
437     /**
438      * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
439      * payload's DNS questions contain the QNAMEs specified in {@code qnames} and qtype
440      * equals {@code qtype}. Examines the payload starting at the offset in R0.
441      * R = 1 means check for "contain".
442      * Drops packets if packets are corrupted.
443      */
addJumpIfPktAtR0ContainDnsQ(@onNull byte[] qnames, int qtype, short tgt)444     public final Type addJumpIfPktAtR0ContainDnsQ(@NonNull byte[] qnames, int qtype, short tgt) {
445         validateNames(qnames);
446         return append(new Instruction(ExtendedOpcodes.JDNSQMATCH, Rbit1).setTargetLabel(tgt).addU8(
447                 qtype).setBytesImm(qnames));
448     }
449 
450     /**
451      * Same as {@link #addJumpIfPktAtR0ContainDnsQ} except passes packets if packets are
452      * corrupted.
453      */
addJumpIfPktAtR0ContainDnsQSafe(@onNull byte[] qnames, int qtype, short tgt)454     public final Type addJumpIfPktAtR0ContainDnsQSafe(@NonNull byte[] qnames, int qtype,
455             short tgt) {
456         validateNames(qnames);
457         return append(new Instruction(ExtendedOpcodes.JDNSQMATCHSAFE, Rbit1).setTargetLabel(
458                 tgt).addU8(qtype).setBytesImm(qnames));
459     }
460 
461     /**
462      * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
463      * payload's DNS answers/authority/additional records do NOT contain the NAMEs
464      * specified in {@code Names}. Examines the payload starting at the offset in R0.
465      * R = 0 means check for "does not contain".
466      * Drops packets if packets are corrupted.
467      */
addJumpIfPktAtR0DoesNotContainDnsA(@onNull byte[] names, short tgt)468     public final Type addJumpIfPktAtR0DoesNotContainDnsA(@NonNull byte[] names, short tgt) {
469         validateNames(names);
470         return append(new Instruction(ExtendedOpcodes.JDNSAMATCH, Rbit0).setTargetLabel(tgt)
471                         .setBytesImm(names));
472     }
473 
474     /**
475      * Same as {@link #addJumpIfPktAtR0DoesNotContainDnsA} except passes packets if packets are
476      * corrupted.
477      */
addJumpIfPktAtR0DoesNotContainDnsASafe(@onNull byte[] names, short tgt)478     public final Type addJumpIfPktAtR0DoesNotContainDnsASafe(@NonNull byte[] names, short tgt) {
479         validateNames(names);
480         return append(new Instruction(ExtendedOpcodes.JDNSAMATCHSAFE, Rbit0).setTargetLabel(tgt)
481                 .setBytesImm(names));
482     }
483 
484     /**
485      * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
486      * payload's DNS answers/authority/additional records contain the NAMEs
487      * specified in {@code Names}. Examines the payload starting at the offset in R0.
488      * R = 1 means check for "contain".
489      * Drops packets if packets are corrupted.
490      */
addJumpIfPktAtR0ContainDnsA(@onNull byte[] names, short tgt)491     public final Type addJumpIfPktAtR0ContainDnsA(@NonNull byte[] names, short tgt) {
492         validateNames(names);
493         return append(new Instruction(ExtendedOpcodes.JDNSAMATCH, Rbit1).setTargetLabel(
494                 tgt).setBytesImm(names));
495     }
496 
497     /**
498      * Same as {@link #addJumpIfPktAtR0ContainDnsA} except passes packets if packets are
499      * corrupted.
500      */
addJumpIfPktAtR0ContainDnsASafe(@onNull byte[] names, short tgt)501     public final Type addJumpIfPktAtR0ContainDnsASafe(@NonNull byte[] names, short tgt) {
502         validateNames(names);
503         return append(new Instruction(ExtendedOpcodes.JDNSAMATCHSAFE, Rbit1).setTargetLabel(
504                 tgt).setBytesImm(names));
505     }
506 
507     /**
508      * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
509      * packet at an offset specified by register0 match {@code bytes}.
510      * R=1 means check for equal.
511      */
addJumpIfBytesAtR0Equal(@onNull byte[] bytes, short tgt)512     public final Type addJumpIfBytesAtR0Equal(@NonNull byte[] bytes, short tgt)
513             throws IllegalInstructionException {
514         validateBytes(bytes);
515         return append(new Instruction(Opcodes.JBSMATCH, R1).addUnsigned(
516                 bytes.length).setTargetLabel(tgt).setBytesImm(bytes));
517     }
518 
addJumpIfBytesAtR0EqualsHelper(@onNull List<byte[]> bytesList, short tgt, boolean jumpOnMatch)519     private Type addJumpIfBytesAtR0EqualsHelper(@NonNull List<byte[]> bytesList, short tgt,
520             boolean jumpOnMatch) {
521         final List<byte[]> deduplicatedList = validateDeduplicateBytesList(bytesList);
522         final int elementSize = deduplicatedList.get(0).length;
523         final int totalElements = deduplicatedList.size();
524         final int totalSize = elementSize * totalElements;
525         final ByteBuffer buffer = ByteBuffer.allocate(totalSize);
526         for (byte[] array : deduplicatedList) {
527             buffer.put(array);
528         }
529         final Rbit rbit = jumpOnMatch ? Rbit1 : Rbit0;
530         final byte[] combinedBytes = buffer.array();
531         return append(new Instruction(Opcodes.JBSMATCH, rbit)
532                 .addUnsigned((totalElements - 1) << 11 | elementSize)
533                 .setTargetLabel(tgt)
534                 .setBytesImm(combinedBytes));
535     }
536 
537     /**
538      * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
539      * packet at an offset specified by register0 match any of the elements in {@code bytesSet}.
540      * R=1 means check for equal.
541      */
addJumpIfBytesAtR0EqualsAnyOf(@onNull List<byte[]> bytesList, short tgt)542     public final Type addJumpIfBytesAtR0EqualsAnyOf(@NonNull List<byte[]> bytesList, short tgt) {
543         return addJumpIfBytesAtR0EqualsHelper(bytesList, tgt, true /* jumpOnMatch */);
544     }
545 
546     /**
547      * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
548      * packet at an offset specified by register0 match none of the elements in {@code bytesSet}.
549      * R=0 means check for not equal.
550      */
addJumpIfBytesAtR0EqualsNoneOf(@onNull List<byte[]> bytesList, short tgt)551     public final Type addJumpIfBytesAtR0EqualsNoneOf(@NonNull List<byte[]> bytesList, short tgt) {
552         return addJumpIfBytesAtR0EqualsHelper(bytesList, tgt, false /* jumpOnMatch */);
553     }
554 
555     /**
556      * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
557      * packet at an offset specified by {@code offset} match any of the elements in
558      * {@code bytesSet}.
559      */
addJumpIfBytesAtOffsetEqualsAnyOf(int offset, @NonNull List<byte[]> bytesList, short tgt)560     public abstract Type addJumpIfBytesAtOffsetEqualsAnyOf(int offset,
561             @NonNull List<byte[]> bytesList, short tgt) throws IllegalInstructionException;
562 
563     /**
564      * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
565      * packet at an offset specified by {@code offset} match none of the elements in
566      * {@code bytesSet}.
567      */
addJumpIfBytesAtOffsetEqualsNoneOf(int offset, @NonNull List<byte[]> bytesList, short tgt)568     public abstract Type addJumpIfBytesAtOffsetEqualsNoneOf(int offset,
569             @NonNull List<byte[]> bytesList, short tgt) throws IllegalInstructionException;
570 
571     /**
572      * Check if the byte is valid dns character: A-Z,0-9,-,_,%,@
573      */
isValidDnsCharacter(byte c)574     private static boolean isValidDnsCharacter(byte c) {
575         return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '%'
576                 || c == '@';
577     }
578 
validateNames(@onNull byte[] names)579     static void validateNames(@NonNull byte[] names) {
580         final int len = names.length;
581         if (len < 4) {
582             throw new IllegalArgumentException("qnames must have at least length 4");
583         }
584         final String errorMessage = "qname: " + HexDump.toHexString(names)
585                 + "is not null-terminated list of TLV-encoded names";
586         int i = 0;
587         while (i < len - 1) {
588             int label_len = names[i++];
589             // byte == 0xff means it is a '*' wildcard
590             if (label_len == -1) continue;
591             if (label_len < 1 || label_len > 63) {
592                 throw new IllegalArgumentException(
593                         "label len: " + label_len + " must be between 1 and 63");
594             }
595             if (i + label_len >= len - 1) {
596                 throw new IllegalArgumentException(errorMessage);
597             }
598             while (label_len-- > 0) {
599                 if (!isValidDnsCharacter(names[i++])) {
600                     throw new IllegalArgumentException("qname: " + HexDump.toHexString(names)
601                             + " contains invalid character");
602                 }
603             }
604             if (names[i] == 0) {
605                 i++; // skip null terminator.
606             }
607         }
608         if (names[len - 1] != 0) {
609             throw new IllegalArgumentException(errorMessage);
610         }
611     }
612 
addJumpIfOneOfHelper(Register reg, @NonNull Set<Long> values, boolean jumpOnMatch, short tgt)613     private Type addJumpIfOneOfHelper(Register reg, @NonNull Set<Long> values,
614             boolean jumpOnMatch, short tgt) {
615         if (values == null || values.size() < 2 || values.size() > 33)  {
616             throw new IllegalArgumentException(
617                     "size of values set must be >= 2 and <= 33, current size: " + values.size());
618         }
619         final Long max = Collections.max(values);
620         final Long min = Collections.min(values);
621         checkRange("max value in set", max, 0, 4294967295L);
622         checkRange("min value in set", min, 0, 4294967295L);
623         // Since sets are always of size > 1 and in range [0, uint32_max], max is guaranteed > 0,
624         // so maxImmSize can never be 0.
625         final int maxImmSize = calculateImmSize(max.intValue(), false);
626 
627         // imm3(u8): top 5 bits - number of following u8/be16/be32 values - 2
628         // middle 2 bits - 1..4 length of immediates - 1
629         // bottom 1 bit - =0 jmp if in set, =1 if not in set
630         Instruction instruction = new Instruction(ExtendedOpcodes.JONEOF, reg)
631                 .setTargetLabel(tgt)
632                 .addU8((values.size() - 2) << 3 | (maxImmSize - 1) << 1 | (jumpOnMatch ? 0 : 1));
633         for (Long v : values) {
634             switch (maxImmSize) {
635                 case 1:
636                     instruction.addU8(v.intValue());
637                     break;
638                 case 2:
639                     instruction.addU16(v.intValue());
640                     break;
641                 // case 3: instruction.addU24(v); break; -- not supported by generator
642                 case 4:
643                     instruction.addU32(v);
644                     break;
645                 default:
646                     throw new IllegalArgumentException(
647                             "immLen is not in {1, 2, 4}, immLen: " + maxImmSize);
648             }
649         }
650         return append(instruction);
651     }
652 
653     /**
654      * Add an instruction to the end of the program to jump to {@code tgt} if {@code reg} is
655      * one of the {@code values}.
656      */
addJumpIfOneOf(Register reg, @NonNull Set<Long> values, short tgt)657     public final Type addJumpIfOneOf(Register reg, @NonNull Set<Long> values, short tgt) {
658         return addJumpIfOneOfHelper(reg, values, true /* jumpOnMatch */, tgt);
659     }
660 
661     /**
662      * Add an instruction to the end of the program to jump to {@code tgt} if {@code reg} is
663      * not one of the {@code values}.
664      */
addJumpIfNoneOf(Register reg, @NonNull Set<Long> values, short tgt)665     public final Type addJumpIfNoneOf(Register reg, @NonNull Set<Long> values, short tgt) {
666         return addJumpIfOneOfHelper(reg, values, false /* jumpOnMatch */, tgt);
667     }
668 
669     @Override
addR0ArithR1(Opcodes opcode)670     void addR0ArithR1(Opcodes opcode) {
671         append(new Instruction(opcode, R0));  // APFv6+: R0 op= R1
672     }
673 
674     @Override
addAdd(long val)675     public final Type addAdd(long val) {
676         if (val == 0) return self();
677         return append(new Instruction(Opcodes.ADD).addTwosCompSigned(val));
678     }
679 
680     @Override
addAnd(long val)681     public final Type addAnd(long val) {
682         if (val == 0) return addLoadImmediate(R0, 0);
683         return append(new Instruction(Opcodes.AND).addTwosCompSigned(val));
684     }
685 
686     /**
687      * Add an instruction to the end of the program to increment the counter value and
688      * immediately return PASS.
689      *
690      * @param counter the counter enum to be incremented.
691      */
692     @Override
addCountAndPass(ApfCounterTracker.Counter counter)693     public final Type addCountAndPass(ApfCounterTracker.Counter counter) {
694         checkPassCounterRange(counter);
695         return addCountAndPass(counter.value());
696     }
697 
698     /**
699      * Add an instruction to the end of the program to increment the counter value and
700      * immediately return DROP.
701      *
702      * @param counter the counter enum to be incremented.
703      */
704     @Override
addCountAndDrop(ApfCounterTracker.Counter counter)705     public final Type addCountAndDrop(ApfCounterTracker.Counter counter) {
706         checkDropCounterRange(counter);
707         return addCountAndDrop(counter.value());
708     }
709 
710     @Override
addLoadCounter(Register register, ApfCounterTracker.Counter counter)711     public final Type addLoadCounter(Register register, ApfCounterTracker.Counter counter)
712             throws IllegalInstructionException {
713         return append(new Instruction(Opcodes.LDDW, register).addUnsigned(counter.value()));
714     }
715 
716     @Override
addStoreCounter(ApfCounterTracker.Counter counter, Register register)717     public final Type addStoreCounter(ApfCounterTracker.Counter counter, Register register)
718             throws IllegalInstructionException {
719         return append(new Instruction(Opcodes.STDW, register).addUnsigned(counter.value()));
720     }
721 
722     /**
723      * This method is noop in APFv6.
724      */
725     @Override
addCountTrampoline()726     public final Type addCountTrampoline() {
727         return self();
728     }
729 
730     @Override
getDefaultPacketHandlingSizeOverEstimate()731     public final int getDefaultPacketHandlingSizeOverEstimate() {
732         // addCountAndPass(PASSED_IPV6_ICMP); -> 2 bytes
733         return 2;
734     }
735 }
736