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