Merge "Android packet filtering program interpreter test & program generator" into mm-wireless-dev
This commit is contained in:
883
services/net/java/android/net/apf/ApfGenerator.java
Normal file
883
services/net/java/android/net/apf/ApfGenerator.java
Normal file
@ -0,0 +1,883 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.net.apf;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APF assembler/generator. A tool for generating an APF program.
|
||||||
|
*
|
||||||
|
* Call add*() functions to add instructions to the program, then call
|
||||||
|
* {@link generate} to get the APF bytecode for the program.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public class ApfGenerator {
|
||||||
|
/**
|
||||||
|
* This exception is thrown when an attempt is made to generate an illegal instruction.
|
||||||
|
*/
|
||||||
|
public static class IllegalInstructionException extends Exception {
|
||||||
|
IllegalInstructionException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private enum Opcodes {
|
||||||
|
LABEL(-1),
|
||||||
|
LDB(1), // Load 1 byte from immediate offset, e.g. "ldb R0, [5]"
|
||||||
|
LDH(2), // Load 2 bytes from immediate offset, e.g. "ldh R0, [5]"
|
||||||
|
LDW(3), // Load 4 bytes from immediate offset, e.g. "ldw R0, [5]"
|
||||||
|
LDBX(4), // Load 1 byte from immediate offset plus register, e.g. "ldbx R0, [5]R0"
|
||||||
|
LDHX(5), // Load 2 byte from immediate offset plus register, e.g. "ldhx R0, [5]R0"
|
||||||
|
LDWX(6), // Load 4 byte from immediate offset plus register, e.g. "ldwx R0, [5]R0"
|
||||||
|
ADD(7), // Add, e.g. "add R0,5"
|
||||||
|
MUL(8), // Multiply, e.g. "mul R0,5"
|
||||||
|
DIV(9), // Divide, e.g. "div R0,5"
|
||||||
|
AND(10), // And, e.g. "and R0,5"
|
||||||
|
OR(11), // Or, e.g. "or R0,5"
|
||||||
|
SH(12), // Left shift, e.g, "sh R0, 5" or "sh R0, -5" (shifts right)
|
||||||
|
LI(13), // Load immediate, e.g. "li R0,5" (immediate encoded as signed value)
|
||||||
|
JMP(14), // Jump, e.g. "jmp label"
|
||||||
|
JEQ(15), // Compare equal and branch, e.g. "jeq R0,5,label"
|
||||||
|
JNE(16), // Compare not equal and branch, e.g. "jne R0,5,label"
|
||||||
|
JGT(17), // Compare greater than and branch, e.g. "jgt R0,5,label"
|
||||||
|
JLT(18), // Compare less than and branch, e.g. "jlt R0,5,label"
|
||||||
|
JSET(19), // Compare any bits set and branch, e.g. "jset R0,5,label"
|
||||||
|
JNEBS(20), // Compare not equal byte sequence, e.g. "jnebs R0,5,label,0x1122334455"
|
||||||
|
EXT(21); // Followed by immediate indicating ExtendedOpcodes.
|
||||||
|
|
||||||
|
final int value;
|
||||||
|
|
||||||
|
private Opcodes(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Extended opcodes. Primary opcode is Opcodes.EXT. ExtendedOpcodes are encoded in the immediate
|
||||||
|
// field.
|
||||||
|
private enum ExtendedOpcodes {
|
||||||
|
LDM(0), // Load from memory, e.g. "ldm R0,5"
|
||||||
|
STM(16), // Store to memory, e.g. "stm R0,5"
|
||||||
|
NOT(32), // Not, e.g. "not R0"
|
||||||
|
NEG(33), // Negate, e.g. "neg R0"
|
||||||
|
SWAP(34), // Swap, e.g. "swap R0,R1"
|
||||||
|
MOVE(35); // Move, e.g. "move R0,R1"
|
||||||
|
|
||||||
|
final int value;
|
||||||
|
|
||||||
|
private ExtendedOpcodes(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public enum Register {
|
||||||
|
R0(0),
|
||||||
|
R1(1);
|
||||||
|
|
||||||
|
final int value;
|
||||||
|
|
||||||
|
private Register(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private class Instruction {
|
||||||
|
private final byte mOpcode; // A "Opcode" value.
|
||||||
|
private final byte mRegister; // A "Register" value.
|
||||||
|
private boolean mHasImm;
|
||||||
|
private byte mImmSize;
|
||||||
|
private boolean mImmSigned;
|
||||||
|
private int mImm;
|
||||||
|
// When mOpcode is a jump:
|
||||||
|
private byte mTargetLabelSize;
|
||||||
|
private String mTargetLabel;
|
||||||
|
// When mOpcode == Opcodes.LABEL:
|
||||||
|
private String mLabel;
|
||||||
|
// When mOpcode == Opcodes.JNEBS:
|
||||||
|
private byte[] mCompareBytes;
|
||||||
|
// Offset in bytes from the begining of this program. Set by {@link ApfGenerator#generate}.
|
||||||
|
int offset;
|
||||||
|
|
||||||
|
Instruction(Opcodes opcode, Register register) {
|
||||||
|
mOpcode = (byte)opcode.value;
|
||||||
|
mRegister = (byte)register.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instruction(Opcodes opcode) {
|
||||||
|
this(opcode, Register.R0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setImm(int imm, boolean signed) {
|
||||||
|
mHasImm = true;
|
||||||
|
mImm = imm;
|
||||||
|
mImmSigned = signed;
|
||||||
|
mImmSize = calculateImmSize(imm, signed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUnsignedImm(int imm) {
|
||||||
|
setImm(imm, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSignedImm(int imm) {
|
||||||
|
setImm(imm, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLabel(String label) throws IllegalInstructionException {
|
||||||
|
if (mLabels.containsKey(label)) {
|
||||||
|
throw new IllegalInstructionException("duplicate label " + label);
|
||||||
|
}
|
||||||
|
if (mOpcode != Opcodes.LABEL.value) {
|
||||||
|
throw new IllegalStateException("adding label to non-label instruction");
|
||||||
|
}
|
||||||
|
mLabel = label;
|
||||||
|
mLabels.put(label, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTargetLabel(String label) {
|
||||||
|
mTargetLabel = label;
|
||||||
|
mTargetLabelSize = 4; // May shrink later on in generate().
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCompareBytes(byte[] bytes) {
|
||||||
|
if (mOpcode != Opcodes.JNEBS.value) {
|
||||||
|
throw new IllegalStateException("adding compare bytes to non-JNEBS instruction");
|
||||||
|
}
|
||||||
|
mCompareBytes = bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return size of instruction in bytes.
|
||||||
|
*/
|
||||||
|
int size() {
|
||||||
|
if (mOpcode == Opcodes.LABEL.value) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int size = 1;
|
||||||
|
if (mHasImm) {
|
||||||
|
size += generatedImmSize();
|
||||||
|
}
|
||||||
|
if (mTargetLabel != null) {
|
||||||
|
size += generatedImmSize();
|
||||||
|
}
|
||||||
|
if (mCompareBytes != null) {
|
||||||
|
size += mCompareBytes.length;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize immediate value field so that it's only as big as required to
|
||||||
|
* contain the offset of the jump destination.
|
||||||
|
* @return {@code true} if shrunk.
|
||||||
|
*/
|
||||||
|
boolean shrink() throws IllegalInstructionException {
|
||||||
|
if (mTargetLabel == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int oldSize = size();
|
||||||
|
int oldTargetLabelSize = mTargetLabelSize;
|
||||||
|
mTargetLabelSize = calculateImmSize(calculateTargetLabelOffset(), false);
|
||||||
|
if (mTargetLabelSize > oldTargetLabelSize) {
|
||||||
|
throw new IllegalStateException("instruction grew");
|
||||||
|
}
|
||||||
|
return size() < oldSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assemble value for instruction size field.
|
||||||
|
*/
|
||||||
|
private byte generateImmSizeField() {
|
||||||
|
byte immSize = generatedImmSize();
|
||||||
|
// Encode size field to fit in 2 bits: 0->0, 1->1, 2->2, 3->4.
|
||||||
|
return immSize == 4 ? 3 : immSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assemble first byte of generated instruction.
|
||||||
|
*/
|
||||||
|
private byte generateInstructionByte() {
|
||||||
|
byte sizeField = generateImmSizeField();
|
||||||
|
return (byte)((mOpcode << 3) | (sizeField << 1) | mRegister);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write {@code value} at offset {@code writingOffset} into {@code bytecode}.
|
||||||
|
* {@link generatedImmSize} bytes are written. {@code value} is truncated to
|
||||||
|
* {@code generatedImmSize} bytes. {@code value} is treated simply as a
|
||||||
|
* 32-bit value, so unsigned values should be zero extended and the truncation
|
||||||
|
* should simply throw away their zero-ed upper bits, and signed values should
|
||||||
|
* be sign extended and the truncation should simply throw away their signed
|
||||||
|
* upper bits.
|
||||||
|
*/
|
||||||
|
private int writeValue(int value, byte[] bytecode, int writingOffset) {
|
||||||
|
for (int i = generatedImmSize() - 1; i >= 0; i--) {
|
||||||
|
bytecode[writingOffset++] = (byte)((value >> (i * 8)) & 255);
|
||||||
|
}
|
||||||
|
return writingOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate bytecode for this instruction at offset {@link offset}.
|
||||||
|
*/
|
||||||
|
void generate(byte[] bytecode) throws IllegalInstructionException {
|
||||||
|
if (mOpcode == Opcodes.LABEL.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int writingOffset = offset;
|
||||||
|
bytecode[writingOffset++] = generateInstructionByte();
|
||||||
|
if (mTargetLabel != null) {
|
||||||
|
writingOffset = writeValue(calculateTargetLabelOffset(), bytecode, writingOffset);
|
||||||
|
}
|
||||||
|
if (mHasImm) {
|
||||||
|
writingOffset = writeValue(mImm, bytecode, writingOffset);
|
||||||
|
}
|
||||||
|
if (mCompareBytes != null) {
|
||||||
|
System.arraycopy(mCompareBytes, 0, bytecode, writingOffset, mCompareBytes.length);
|
||||||
|
writingOffset += mCompareBytes.length;
|
||||||
|
}
|
||||||
|
if ((writingOffset - offset) != size()) {
|
||||||
|
throw new IllegalStateException("wrote " + (writingOffset - offset) +
|
||||||
|
" but should have written " + size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the size of either the immediate field or the target label field, if either is
|
||||||
|
* present. Most instructions have either an immediate or a target label field, but for the
|
||||||
|
* instructions that have both, the size of the target label field must be the same as the
|
||||||
|
* size of the immediate field, because there is only one length field in the instruction
|
||||||
|
* byte, hence why this function simply takes the maximum of the two sizes, so neither is
|
||||||
|
* truncated.
|
||||||
|
*/
|
||||||
|
private byte generatedImmSize() {
|
||||||
|
return mImmSize > mTargetLabelSize ? mImmSize : mTargetLabelSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calculateTargetLabelOffset() throws IllegalInstructionException {
|
||||||
|
Instruction targetLabelInstruction;
|
||||||
|
if (mTargetLabel == DROP_LABEL) {
|
||||||
|
targetLabelInstruction = mDropLabel;
|
||||||
|
} else if (mTargetLabel == PASS_LABEL) {
|
||||||
|
targetLabelInstruction = mPassLabel;
|
||||||
|
} else {
|
||||||
|
targetLabelInstruction = mLabels.get(mTargetLabel);
|
||||||
|
}
|
||||||
|
if (targetLabelInstruction == null) {
|
||||||
|
throw new IllegalInstructionException("label not found: " + mTargetLabel);
|
||||||
|
}
|
||||||
|
// Calculate distance from end of this instruction to instruction.offset.
|
||||||
|
final int targetLabelOffset = targetLabelInstruction.offset - (offset + size());
|
||||||
|
if (targetLabelOffset < 0) {
|
||||||
|
throw new IllegalInstructionException("backward branches disallowed; label: " +
|
||||||
|
mTargetLabel);
|
||||||
|
}
|
||||||
|
return targetLabelOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte calculateImmSize(int imm, boolean signed) {
|
||||||
|
if (imm == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (signed && (imm >= -128 && imm <= 127) ||
|
||||||
|
!signed && (imm >= 0 && imm <= 255)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (signed && (imm >= -32768 && imm <= 32767) ||
|
||||||
|
!signed && (imm >= 0 && imm <= 65535)) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jump to this label to terminate the program and indicate the packet
|
||||||
|
* should be dropped.
|
||||||
|
*/
|
||||||
|
public static final String DROP_LABEL = "__DROP__";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jump to this label to terminate the program and indicate the packet
|
||||||
|
* should be passed to the AP.
|
||||||
|
*/
|
||||||
|
public static final String PASS_LABEL = "__PASS__";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of memory slots available for access via APF stores to memory and loads from memory.
|
||||||
|
* The memory slots are numbered 0 to {@code MEMORY_SLOTS} - 1. This must be kept in sync with
|
||||||
|
* the APF interpreter.
|
||||||
|
*/
|
||||||
|
public static final int MEMORY_SLOTS = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memory slot number that is prefilled with the IPv4 header length.
|
||||||
|
* Note that this memory slot may be overwritten by a program that
|
||||||
|
* executes stores to this memory slot. This must be kept in sync with
|
||||||
|
* the APF interpreter.
|
||||||
|
*/
|
||||||
|
public static final int IPV4_HEADER_SIZE_MEMORY_SLOT = 13;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memory slot number that is prefilled with the size of the packet being filtered in bytes.
|
||||||
|
* Note that this memory slot may be overwritten by a program that
|
||||||
|
* executes stores to this memory slot. This must be kept in sync with the APF interpreter.
|
||||||
|
*/
|
||||||
|
public static final int PACKET_SIZE_MEMORY_SLOT = 14;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memory slot number that is prefilled with the age of the filter in seconds. The age of the
|
||||||
|
* filter is the time since the filter was installed until now.
|
||||||
|
* Note that this memory slot may be overwritten by a program that
|
||||||
|
* executes stores to this memory slot. This must be kept in sync with the APF interpreter.
|
||||||
|
*/
|
||||||
|
public static final int FILTER_AGE_MEMORY_SLOT = 15;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First memory slot containing prefilled values. Can be used in range comparisons to determine
|
||||||
|
* if memory slot index is within prefilled slots.
|
||||||
|
*/
|
||||||
|
public static final int FIRST_PREFILLED_MEMORY_SLOT = IPV4_HEADER_SIZE_MEMORY_SLOT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last memory slot containing prefilled values. Can be used in range comparisons to determine
|
||||||
|
* if memory slot index is within prefilled slots.
|
||||||
|
*/
|
||||||
|
public static final int LAST_PREFILLED_MEMORY_SLOT = FILTER_AGE_MEMORY_SLOT;
|
||||||
|
|
||||||
|
private final ArrayList<Instruction> mInstructions = new ArrayList<Instruction>();
|
||||||
|
private final HashMap<String, Instruction> mLabels = new HashMap<String, Instruction>();
|
||||||
|
private final Instruction mDropLabel = new Instruction(Opcodes.LABEL);
|
||||||
|
private final Instruction mPassLabel = new Instruction(Opcodes.LABEL);
|
||||||
|
private boolean mGenerated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set version of APF instruction set to generate instructions for. Returns {@code true}
|
||||||
|
* if generating for this version is supported, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public boolean setApfVersion(int version) {
|
||||||
|
// This version number syncs up with APF_VERSION in hardware/google/apf/apf_interpreter.h
|
||||||
|
return version == 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addInstruction(Instruction instruction) {
|
||||||
|
if (mGenerated) {
|
||||||
|
throw new IllegalStateException("Program already generated");
|
||||||
|
}
|
||||||
|
mInstructions.add(instruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a label at the current end of the program. Jumps can jump to this label. Labels are
|
||||||
|
* their own separate instructions, though with size 0. This facilitates having labels with
|
||||||
|
* no corresponding code to execute, for example a label at the end of a program. For example
|
||||||
|
* an {@link ApfGenerator} might be passed to a function that adds a filter like so:
|
||||||
|
* <pre>
|
||||||
|
* load from packet
|
||||||
|
* compare loaded data, jump if not equal to "next_filter"
|
||||||
|
* load from packet
|
||||||
|
* compare loaded data, jump if not equal to "next_filter"
|
||||||
|
* jump to drop label
|
||||||
|
* define "next_filter" here
|
||||||
|
* </pre>
|
||||||
|
* In this case "next_filter" may not have any generated code associated with it.
|
||||||
|
*/
|
||||||
|
public ApfGenerator defineLabel(String name) throws IllegalInstructionException {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.LABEL);
|
||||||
|
instruction.setLabel(name);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an unconditional jump instruction to the end of the program.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addJump(String target) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.JMP);
|
||||||
|
instruction.setTargetLabel(target);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to load the byte at offset {@code offset}
|
||||||
|
* bytes from the begining of the packet into {@code register}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addLoad8(Register register, int offset) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.LDB, register);
|
||||||
|
instruction.setUnsignedImm(offset);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to load 16-bits at offset {@code offset}
|
||||||
|
* bytes from the begining of the packet into {@code register}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addLoad16(Register register, int offset) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.LDH, register);
|
||||||
|
instruction.setUnsignedImm(offset);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to load 32-bits at offset {@code offset}
|
||||||
|
* bytes from the begining of the packet into {@code register}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addLoad32(Register register, int offset) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.LDW, register);
|
||||||
|
instruction.setUnsignedImm(offset);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to load a byte from the packet into
|
||||||
|
* {@code register}. The offset of the loaded byte from the begining of the packet is
|
||||||
|
* the sum of {@code offset} and the value in register R1.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addLoad8Indexed(Register register, int offset) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.LDBX, register);
|
||||||
|
instruction.setUnsignedImm(offset);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to load 16-bits from the packet into
|
||||||
|
* {@code register}. The offset of the loaded 16-bits from the begining of the packet is
|
||||||
|
* the sum of {@code offset} and the value in register R1.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addLoad16Indexed(Register register, int offset) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.LDHX, register);
|
||||||
|
instruction.setUnsignedImm(offset);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to load 32-bits from the packet into
|
||||||
|
* {@code register}. The offset of the loaded 32-bits from the begining of the packet is
|
||||||
|
* the sum of {@code offset} and the value in register R1.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addLoad32Indexed(Register register, int offset) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.LDWX, register);
|
||||||
|
instruction.setUnsignedImm(offset);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to add {@code value} to register R0.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addAdd(int value) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.ADD);
|
||||||
|
instruction.setSignedImm(value);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to multiply register R0 by {@code value}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addMul(int value) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.MUL);
|
||||||
|
instruction.setSignedImm(value);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to divide register R0 by {@code value}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addDiv(int value) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.DIV);
|
||||||
|
instruction.setSignedImm(value);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to logically and register R0 with {@code value}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addAnd(int value) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.AND);
|
||||||
|
instruction.setUnsignedImm(value);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to logically or register R0 with {@code value}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addOr(int value) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.OR);
|
||||||
|
instruction.setUnsignedImm(value);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to shift left register R0 by {@code value} bits.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addLeftShift(int value) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.SH);
|
||||||
|
instruction.setSignedImm(value);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to shift right register R0 by {@code value}
|
||||||
|
* bits.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addRightShift(int value) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.SH);
|
||||||
|
instruction.setSignedImm(-value);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to add register R1 to register R0.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addAddR1() {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.ADD, Register.R1);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to multiply register R0 by register R1.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addMulR1() {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.MUL, Register.R1);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to divide register R0 by register R1.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addDivR1() {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.DIV, Register.R1);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to logically and register R0 with register R1
|
||||||
|
* and store the result back into register R0.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addAndR1() {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.AND, Register.R1);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to logically or register R0 with register R1
|
||||||
|
* and store the result back into register R0.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addOrR1() {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.OR, Register.R1);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to shift register R0 left by the value in
|
||||||
|
* register R1.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addLeftShiftR1() {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.SH, Register.R1);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to move {@code value} into {@code register}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addLoadImmediate(Register register, int value) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.LI, register);
|
||||||
|
instruction.setSignedImm(value);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to jump to {@code target} if register R0's
|
||||||
|
* value equals {@code value}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addJumpIfR0Equals(int value, String target) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.JEQ);
|
||||||
|
instruction.setUnsignedImm(value);
|
||||||
|
instruction.setTargetLabel(target);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to jump to {@code target} if register R0's
|
||||||
|
* value does not equal {@code value}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addJumpIfR0NotEquals(int value, String target) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.JNE);
|
||||||
|
instruction.setUnsignedImm(value);
|
||||||
|
instruction.setTargetLabel(target);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to jump to {@code target} if register R0's
|
||||||
|
* value is greater than {@code value}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addJumpIfR0GreaterThan(int value, String target) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.JGT);
|
||||||
|
instruction.setUnsignedImm(value);
|
||||||
|
instruction.setTargetLabel(target);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to jump to {@code target} if register R0's
|
||||||
|
* value is less than {@code value}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addJumpIfR0LessThan(int value, String target) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.JLT);
|
||||||
|
instruction.setUnsignedImm(value);
|
||||||
|
instruction.setTargetLabel(target);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to jump to {@code target} if register R0's
|
||||||
|
* value has any bits set that are also set in {@code value}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addJumpIfR0AnyBitsSet(int value, String target) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.JSET);
|
||||||
|
instruction.setUnsignedImm(value);
|
||||||
|
instruction.setTargetLabel(target);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to jump to {@code target} if register R0's
|
||||||
|
* value equals register R1's value.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addJumpIfR0EqualsR1(String target) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.JEQ, Register.R1);
|
||||||
|
instruction.setTargetLabel(target);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to jump to {@code target} if register R0's
|
||||||
|
* value does not equal register R1's value.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addJumpIfR0NotEqualsR1(String target) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.JNE, Register.R1);
|
||||||
|
instruction.setTargetLabel(target);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to jump to {@code target} if register R0's
|
||||||
|
* value is greater than register R1's value.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addJumpIfR0GreaterThanR1(String target) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.JGT, Register.R1);
|
||||||
|
instruction.setTargetLabel(target);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to jump to {@code target} if register R0's
|
||||||
|
* value is less than register R1's value.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addJumpIfR0LessThanR1(String target) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.JLT, Register.R1);
|
||||||
|
instruction.setTargetLabel(target);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to jump to {@code target} if register R0's
|
||||||
|
* value has any bits set that are also set in R1's value.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addJumpIfR0AnyBitsSetR1(String target) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.JSET, Register.R1);
|
||||||
|
instruction.setTargetLabel(target);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to jump to {@code target} if the bytes of the
|
||||||
|
* packet at, an offset specified by {@code register}, match {@code bytes}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addJumpIfBytesNotEqual(Register register, byte[] bytes, String target) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.JNEBS, register);
|
||||||
|
instruction.setUnsignedImm(bytes.length);
|
||||||
|
instruction.setTargetLabel(target);
|
||||||
|
instruction.setCompareBytes(bytes);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to load memory slot {@code slot} into
|
||||||
|
* {@code register}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addLoadFromMemory(Register register, int slot)
|
||||||
|
throws IllegalInstructionException {
|
||||||
|
if (slot < 0 || slot > (MEMORY_SLOTS - 1)) {
|
||||||
|
throw new IllegalInstructionException("illegal memory slot number: " + slot);
|
||||||
|
}
|
||||||
|
Instruction instruction = new Instruction(Opcodes.EXT, register);
|
||||||
|
instruction.setUnsignedImm(ExtendedOpcodes.LDM.value + slot);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to store {@code register} into memory slot
|
||||||
|
* {@code slot}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addStoreToMemory(Register register, int slot)
|
||||||
|
throws IllegalInstructionException {
|
||||||
|
if (slot < 0 || slot > (MEMORY_SLOTS - 1)) {
|
||||||
|
throw new IllegalInstructionException("illegal memory slot number: " + slot);
|
||||||
|
}
|
||||||
|
Instruction instruction = new Instruction(Opcodes.EXT, register);
|
||||||
|
instruction.setUnsignedImm(ExtendedOpcodes.STM.value + slot);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to logically not {@code register}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addNot(Register register) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.EXT, register);
|
||||||
|
instruction.setUnsignedImm(ExtendedOpcodes.NOT.value);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to negate {@code register}.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addNeg(Register register) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.EXT, register);
|
||||||
|
instruction.setUnsignedImm(ExtendedOpcodes.NEG.value);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to swap the values in register R0 and register R1.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addSwap() {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.EXT);
|
||||||
|
instruction.setUnsignedImm(ExtendedOpcodes.SWAP.value);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an instruction to the end of the program to move the value into
|
||||||
|
* {@code register} from the other register.
|
||||||
|
*/
|
||||||
|
public ApfGenerator addMove(Register register) {
|
||||||
|
Instruction instruction = new Instruction(Opcodes.EXT, register);
|
||||||
|
instruction.setUnsignedImm(ExtendedOpcodes.MOVE.value);
|
||||||
|
addInstruction(instruction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates instruction offset fields using latest instruction sizes.
|
||||||
|
* @return current program length in bytes.
|
||||||
|
*/
|
||||||
|
private int updateInstructionOffsets() {
|
||||||
|
int offset = 0;
|
||||||
|
for (Instruction instruction : mInstructions) {
|
||||||
|
instruction.offset = offset;
|
||||||
|
offset += instruction.size();
|
||||||
|
}
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an overestimate of the size of the generated program. {@link #generate} may return
|
||||||
|
* a program that is smaller.
|
||||||
|
*/
|
||||||
|
public int programLengthOverEstimate() {
|
||||||
|
return updateInstructionOffsets();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the bytecode for the APF program.
|
||||||
|
* @return the bytecode.
|
||||||
|
* @throws IllegalStateException if a label is referenced but not defined.
|
||||||
|
*/
|
||||||
|
public byte[] generate() throws IllegalInstructionException {
|
||||||
|
// Enforce that we can only generate once because we cannot unshrink instructions and
|
||||||
|
// PASS/DROP labels may move further away requiring unshrinking if we add further
|
||||||
|
// instructions.
|
||||||
|
if (mGenerated) {
|
||||||
|
throw new IllegalStateException("Can only generate() once!");
|
||||||
|
}
|
||||||
|
mGenerated = true;
|
||||||
|
int total_size;
|
||||||
|
boolean shrunk;
|
||||||
|
// Shrink the immediate value fields of instructions.
|
||||||
|
// As we shrink the instructions some branch offset
|
||||||
|
// fields may shrink also, thereby shrinking the
|
||||||
|
// instructions further. Loop until we've reached the
|
||||||
|
// minimum size. Rarely will this loop more than a few times.
|
||||||
|
// Limit iterations to avoid O(n^2) behavior.
|
||||||
|
int iterations_remaining = 10;
|
||||||
|
do {
|
||||||
|
total_size = updateInstructionOffsets();
|
||||||
|
// Update drop and pass label offsets.
|
||||||
|
mDropLabel.offset = total_size + 1;
|
||||||
|
mPassLabel.offset = total_size;
|
||||||
|
// Limit run-time in aberant circumstances.
|
||||||
|
if (iterations_remaining-- == 0) break;
|
||||||
|
// Attempt to shrink instructions.
|
||||||
|
shrunk = false;
|
||||||
|
for (Instruction instruction : mInstructions) {
|
||||||
|
if (instruction.shrink()) {
|
||||||
|
shrunk = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (shrunk);
|
||||||
|
// Generate bytecode for instructions.
|
||||||
|
byte[] bytecode = new byte[total_size];
|
||||||
|
for (Instruction instruction : mInstructions) {
|
||||||
|
instruction.generate(bytecode);
|
||||||
|
}
|
||||||
|
return bytecode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,7 @@
|
|||||||
|
#########################################################################
|
||||||
|
# Build FrameworksServicesTests package
|
||||||
|
#########################################################################
|
||||||
|
|
||||||
LOCAL_PATH:= $(call my-dir)
|
LOCAL_PATH:= $(call my-dir)
|
||||||
include $(CLEAR_VARS)
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
@ -21,5 +25,36 @@ LOCAL_PACKAGE_NAME := FrameworksServicesTests
|
|||||||
|
|
||||||
LOCAL_CERTIFICATE := platform
|
LOCAL_CERTIFICATE := platform
|
||||||
|
|
||||||
|
LOCAL_JNI_SHARED_LIBRARIES := libapfjni
|
||||||
|
|
||||||
include $(BUILD_PACKAGE)
|
include $(BUILD_PACKAGE)
|
||||||
|
|
||||||
|
#########################################################################
|
||||||
|
# Build JNI Shared Library
|
||||||
|
#########################################################################
|
||||||
|
|
||||||
|
LOCAL_PATH:= $(LOCAL_PATH)/jni
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE_TAGS := tests
|
||||||
|
|
||||||
|
LOCAL_CFLAGS := -Wall -Werror
|
||||||
|
|
||||||
|
LOCAL_C_INCLUDES := \
|
||||||
|
libpcap \
|
||||||
|
hardware/google/apf
|
||||||
|
|
||||||
|
LOCAL_SRC_FILES := apf_jni.cpp
|
||||||
|
|
||||||
|
LOCAL_SHARED_LIBRARIES := \
|
||||||
|
libnativehelper \
|
||||||
|
liblog
|
||||||
|
|
||||||
|
LOCAL_STATIC_LIBRARIES := \
|
||||||
|
libpcap \
|
||||||
|
libapf
|
||||||
|
|
||||||
|
LOCAL_MODULE := libapfjni
|
||||||
|
|
||||||
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
|
182
services/tests/servicestests/jni/apf_jni.cpp
Normal file
182
services/tests/servicestests/jni/apf_jni.cpp
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016, The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <JNIHelp.h>
|
||||||
|
#include <ScopedUtfChars.h>
|
||||||
|
#include <jni.h>
|
||||||
|
#include <pcap.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string>
|
||||||
|
#include <utils/Log.h>
|
||||||
|
|
||||||
|
#include "apf_interpreter.h"
|
||||||
|
|
||||||
|
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
||||||
|
|
||||||
|
// JNI function acting as simply call-through to native APF interpreter.
|
||||||
|
static jint com_android_server_ApfTest_apfSimulate(
|
||||||
|
JNIEnv* env, jclass, jbyteArray program, jbyteArray packet, jint filter_age) {
|
||||||
|
return accept_packet(
|
||||||
|
(uint8_t*)env->GetByteArrayElements(program, NULL),
|
||||||
|
env->GetArrayLength(program),
|
||||||
|
(uint8_t*)env->GetByteArrayElements(packet, NULL),
|
||||||
|
env->GetArrayLength(packet),
|
||||||
|
filter_age);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScopedPcap {
|
||||||
|
public:
|
||||||
|
ScopedPcap(pcap_t* pcap) : pcap_ptr(pcap) {}
|
||||||
|
~ScopedPcap() {
|
||||||
|
pcap_close(pcap_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
pcap_t* get() const { return pcap_ptr; };
|
||||||
|
private:
|
||||||
|
pcap_t* const pcap_ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScopedFILE {
|
||||||
|
public:
|
||||||
|
ScopedFILE(FILE* fp) : file(fp) {}
|
||||||
|
~ScopedFILE() {
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* get() const { return file; };
|
||||||
|
private:
|
||||||
|
FILE* const file;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void throwException(JNIEnv* env, const std::string& error) {
|
||||||
|
jclass newExcCls = env->FindClass("java/lang/IllegalStateException");
|
||||||
|
if (newExcCls == 0) {
|
||||||
|
abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
env->ThrowNew(newExcCls, error.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static jstring com_android_server_ApfTest_compileToBpf(JNIEnv* env, jclass, jstring jfilter) {
|
||||||
|
ScopedUtfChars filter(env, jfilter);
|
||||||
|
std::string bpf_string;
|
||||||
|
ScopedPcap pcap(pcap_open_dead(DLT_EN10MB, 65535));
|
||||||
|
if (pcap.get() == NULL) {
|
||||||
|
throwException(env, "pcap_open_dead failed");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile "filter" to a BPF program
|
||||||
|
bpf_program bpf;
|
||||||
|
if (pcap_compile(pcap.get(), &bpf, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN)) {
|
||||||
|
throwException(env, "pcap_compile failed");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate BPF program to human-readable format
|
||||||
|
const struct bpf_insn* insn = bpf.bf_insns;
|
||||||
|
for (uint32_t i = 0; i < bpf.bf_len; i++) {
|
||||||
|
bpf_string += bpf_image(insn++, i);
|
||||||
|
bpf_string += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return env->NewStringUTF(bpf_string.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static jboolean com_android_server_ApfTest_compareBpfApf(JNIEnv* env, jclass, jstring jfilter,
|
||||||
|
jstring jpcap_filename, jbyteArray japf_program) {
|
||||||
|
ScopedUtfChars filter(env, jfilter);
|
||||||
|
ScopedUtfChars pcap_filename(env, jpcap_filename);
|
||||||
|
const uint8_t* apf_program = (uint8_t*)env->GetByteArrayElements(japf_program, NULL);
|
||||||
|
const uint32_t apf_program_len = env->GetArrayLength(japf_program);
|
||||||
|
|
||||||
|
// Open pcap file for BPF filtering
|
||||||
|
ScopedFILE bpf_fp(fopen(pcap_filename.c_str(), "rb"));
|
||||||
|
char pcap_error[PCAP_ERRBUF_SIZE];
|
||||||
|
ScopedPcap bpf_pcap(pcap_fopen_offline(bpf_fp.get(), pcap_error));
|
||||||
|
if (bpf_pcap.get() == NULL) {
|
||||||
|
throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open pcap file for APF filtering
|
||||||
|
ScopedFILE apf_fp(fopen(pcap_filename.c_str(), "rb"));
|
||||||
|
ScopedPcap apf_pcap(pcap_fopen_offline(apf_fp.get(), pcap_error));
|
||||||
|
if (apf_pcap.get() == NULL) {
|
||||||
|
throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile "filter" to a BPF program
|
||||||
|
bpf_program bpf;
|
||||||
|
if (pcap_compile(bpf_pcap.get(), &bpf, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN)) {
|
||||||
|
throwException(env, "pcap_compile failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install BPF filter on bpf_pcap
|
||||||
|
if (pcap_setfilter(bpf_pcap.get(), &bpf)) {
|
||||||
|
throwException(env, "pcap_setfilter failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
pcap_pkthdr bpf_header, apf_header;
|
||||||
|
// Run BPF filter to the next matching packet.
|
||||||
|
const uint8_t* bpf_packet = pcap_next(bpf_pcap.get(), &bpf_header);
|
||||||
|
|
||||||
|
// Run APF filter to the next matching packet.
|
||||||
|
const uint8_t* apf_packet;
|
||||||
|
do {
|
||||||
|
apf_packet = pcap_next(apf_pcap.get(), &apf_header);
|
||||||
|
} while (apf_packet != NULL && !accept_packet(
|
||||||
|
apf_program, apf_program_len, apf_packet, apf_header.len, 0));
|
||||||
|
|
||||||
|
// Make sure both filters matched the same packet.
|
||||||
|
if (apf_packet == NULL && bpf_packet == NULL)
|
||||||
|
break;
|
||||||
|
if (apf_packet == NULL || bpf_packet == NULL)
|
||||||
|
return false;
|
||||||
|
if (apf_header.len != bpf_header.len ||
|
||||||
|
apf_header.ts.tv_sec != bpf_header.ts.tv_sec ||
|
||||||
|
apf_header.ts.tv_usec != bpf_header.ts.tv_usec ||
|
||||||
|
memcmp(apf_packet, bpf_packet, apf_header.len))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
|
||||||
|
JNIEnv *env;
|
||||||
|
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||||
|
ALOGE("ERROR: GetEnv failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static JNINativeMethod gMethods[] = {
|
||||||
|
{ "apfSimulate", "([B[BI)I",
|
||||||
|
(void*)com_android_server_ApfTest_apfSimulate },
|
||||||
|
{ "compileToBpf", "(Ljava/lang/String;)Ljava/lang/String;",
|
||||||
|
(void*)com_android_server_ApfTest_compileToBpf },
|
||||||
|
{ "compareBpfApf", "(Ljava/lang/String;Ljava/lang/String;[B)Z",
|
||||||
|
(void*)com_android_server_ApfTest_compareBpfApf },
|
||||||
|
};
|
||||||
|
|
||||||
|
jniRegisterNativeMethods(env, "com/android/server/ApfTest",
|
||||||
|
gMethods, ARRAY_SIZE(gMethods));
|
||||||
|
|
||||||
|
return JNI_VERSION_1_6;
|
||||||
|
}
|
BIN
services/tests/servicestests/res/raw/apf.pcap
Normal file
BIN
services/tests/servicestests/res/raw/apf.pcap
Normal file
Binary file not shown.
560
services/tests/servicestests/src/com/android/server/ApfTest.java
Normal file
560
services/tests/servicestests/src/com/android/server/ApfTest.java
Normal file
@ -0,0 +1,560 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.server;
|
||||||
|
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
import android.test.suitebuilder.annotation.LargeTest;
|
||||||
|
|
||||||
|
import com.android.frameworks.servicestests.R;
|
||||||
|
import android.net.apf.ApfGenerator;
|
||||||
|
import android.net.apf.ApfGenerator.IllegalInstructionException;
|
||||||
|
import android.net.apf.ApfGenerator.Register;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import libcore.io.IoUtils;
|
||||||
|
import libcore.io.Streams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for APF program generator and interpreter.
|
||||||
|
*
|
||||||
|
* Build, install and run with:
|
||||||
|
* runtest frameworks-services -c com.android.server.ApfTest
|
||||||
|
*/
|
||||||
|
public class ApfTest extends AndroidTestCase {
|
||||||
|
@Override
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
// Load up native shared library containing APF interpreter exposed via JNI.
|
||||||
|
System.loadLibrary("apfjni");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expected return codes from APF interpreter.
|
||||||
|
private final static int PASS = 1;
|
||||||
|
private final static int DROP = 0;
|
||||||
|
// Interpreter will just accept packets without link layer headers, so pad fake packet to at
|
||||||
|
// least the minimum packet size.
|
||||||
|
private final static int MIN_PKT_SIZE = 15;
|
||||||
|
|
||||||
|
private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) {
|
||||||
|
assertEquals(expected, apfSimulate(program, packet, filterAge));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertPass(byte[] program, byte[] packet, int filterAge) {
|
||||||
|
assertVerdict(PASS, program, packet, filterAge);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertDrop(byte[] program, byte[] packet, int filterAge) {
|
||||||
|
assertVerdict(DROP, program, packet, filterAge);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertVerdict(int expected, ApfGenerator gen, byte[] packet, int filterAge)
|
||||||
|
throws IllegalInstructionException {
|
||||||
|
assertEquals(expected, apfSimulate(gen.generate(), packet, filterAge));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertPass(ApfGenerator gen, byte[] packet, int filterAge)
|
||||||
|
throws IllegalInstructionException {
|
||||||
|
assertVerdict(PASS, gen, packet, filterAge);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertDrop(ApfGenerator gen, byte[] packet, int filterAge)
|
||||||
|
throws IllegalInstructionException {
|
||||||
|
assertVerdict(DROP, gen, packet, filterAge);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertPass(ApfGenerator gen)
|
||||||
|
throws IllegalInstructionException {
|
||||||
|
assertVerdict(PASS, gen, new byte[MIN_PKT_SIZE], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertDrop(ApfGenerator gen)
|
||||||
|
throws IllegalInstructionException {
|
||||||
|
assertVerdict(DROP, gen, new byte[MIN_PKT_SIZE], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test each instruction by generating a program containing the instruction,
|
||||||
|
* generating bytecode for that program and running it through the
|
||||||
|
* interpreter to verify it functions correctly.
|
||||||
|
*/
|
||||||
|
@LargeTest
|
||||||
|
public void testApfInstructions() throws IllegalInstructionException {
|
||||||
|
// Empty program should pass because having the program counter reach the
|
||||||
|
// location immediately after the program indicates the packet should be
|
||||||
|
// passed to the AP.
|
||||||
|
ApfGenerator gen = new ApfGenerator();
|
||||||
|
assertPass(gen);
|
||||||
|
|
||||||
|
// Test jumping to pass label.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addJump(gen.PASS_LABEL);
|
||||||
|
byte[] program = gen.generate();
|
||||||
|
assertEquals(1, program.length);
|
||||||
|
assertEquals((14 << 3) | (0 << 1) | 0, program[0]);
|
||||||
|
assertPass(program, new byte[MIN_PKT_SIZE], 0);
|
||||||
|
|
||||||
|
// Test jumping to drop label.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addJump(gen.DROP_LABEL);
|
||||||
|
program = gen.generate();
|
||||||
|
assertEquals(2, program.length);
|
||||||
|
assertEquals((14 << 3) | (1 << 1) | 0, program[0]);
|
||||||
|
assertEquals(1, program[1]);
|
||||||
|
assertDrop(program, new byte[15], 15);
|
||||||
|
|
||||||
|
// Test jumping if equal to 0.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test jumping if not equal to 0.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
|
||||||
|
assertPass(gen);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1);
|
||||||
|
gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test jumping if registers equal.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addJumpIfR0EqualsR1(gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test jumping if registers not equal.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
|
||||||
|
assertPass(gen);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1);
|
||||||
|
gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test load immediate.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1234567890);
|
||||||
|
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test add.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addAdd(1234567890);
|
||||||
|
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test subtract.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addAdd(-1234567890);
|
||||||
|
gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test or.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addOr(1234567890);
|
||||||
|
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test and.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1234567890);
|
||||||
|
gen.addAnd(123456789);
|
||||||
|
gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test left shift.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1234567890);
|
||||||
|
gen.addLeftShift(1);
|
||||||
|
gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test right shift.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1234567890);
|
||||||
|
gen.addRightShift(1);
|
||||||
|
gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test multiply.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1234567890);
|
||||||
|
gen.addMul(2);
|
||||||
|
gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test divide.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1234567890);
|
||||||
|
gen.addDiv(2);
|
||||||
|
gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test divide by zero.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addDiv(0);
|
||||||
|
gen.addJump(gen.DROP_LABEL);
|
||||||
|
assertPass(gen);
|
||||||
|
|
||||||
|
// Test add.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R1, 1234567890);
|
||||||
|
gen.addAddR1();
|
||||||
|
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test subtract.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R1, -1234567890);
|
||||||
|
gen.addAddR1();
|
||||||
|
gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test or.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R1, 1234567890);
|
||||||
|
gen.addOrR1();
|
||||||
|
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test and.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1234567890);
|
||||||
|
gen.addLoadImmediate(Register.R1, 123456789);
|
||||||
|
gen.addAndR1();
|
||||||
|
gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test left shift.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1234567890);
|
||||||
|
gen.addLoadImmediate(Register.R1, 1);
|
||||||
|
gen.addLeftShiftR1();
|
||||||
|
gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test right shift.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1234567890);
|
||||||
|
gen.addLoadImmediate(Register.R1, -1);
|
||||||
|
gen.addLeftShiftR1();
|
||||||
|
gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test multiply.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1234567890);
|
||||||
|
gen.addLoadImmediate(Register.R1, 2);
|
||||||
|
gen.addMulR1();
|
||||||
|
gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test divide.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1234567890);
|
||||||
|
gen.addLoadImmediate(Register.R1, 2);
|
||||||
|
gen.addDivR1();
|
||||||
|
gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test divide by zero.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addDivR1();
|
||||||
|
gen.addJump(gen.DROP_LABEL);
|
||||||
|
assertPass(gen);
|
||||||
|
|
||||||
|
// Test byte load.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoad8(Register.R0, 1);
|
||||||
|
gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
|
||||||
|
|
||||||
|
// Test out of bounds load.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoad8(Register.R0, 16);
|
||||||
|
gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
|
||||||
|
assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
|
||||||
|
|
||||||
|
// Test half-word load.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoad16(Register.R0, 1);
|
||||||
|
gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
|
||||||
|
|
||||||
|
// Test word load.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoad32(Register.R0, 1);
|
||||||
|
gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
|
||||||
|
|
||||||
|
// Test byte indexed load.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R1, 1);
|
||||||
|
gen.addLoad8Indexed(Register.R0, 0);
|
||||||
|
gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
|
||||||
|
|
||||||
|
// Test out of bounds indexed load.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R1, 8);
|
||||||
|
gen.addLoad8Indexed(Register.R0, 8);
|
||||||
|
gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
|
||||||
|
assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
|
||||||
|
|
||||||
|
// Test half-word indexed load.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R1, 1);
|
||||||
|
gen.addLoad16Indexed(Register.R0, 0);
|
||||||
|
gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
|
||||||
|
|
||||||
|
// Test word indexed load.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R1, 1);
|
||||||
|
gen.addLoad32Indexed(Register.R0, 0);
|
||||||
|
gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
|
||||||
|
|
||||||
|
// Test jumping if greater than.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
|
||||||
|
assertPass(gen);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1);
|
||||||
|
gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test jumping if less than.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addJumpIfR0LessThan(0, gen.DROP_LABEL);
|
||||||
|
assertPass(gen);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addJumpIfR0LessThan(1, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test jumping if any bits set.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
|
||||||
|
assertPass(gen);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1);
|
||||||
|
gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 3);
|
||||||
|
gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test jumping if register greater than.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
|
||||||
|
assertPass(gen);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 2);
|
||||||
|
gen.addLoadImmediate(Register.R1, 1);
|
||||||
|
gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test jumping if register less than.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
|
||||||
|
assertPass(gen);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R1, 1);
|
||||||
|
gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test jumping if any bits set in register.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R1, 3);
|
||||||
|
gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
|
||||||
|
assertPass(gen);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R1, 3);
|
||||||
|
gen.addLoadImmediate(Register.R0, 1);
|
||||||
|
gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R1, 3);
|
||||||
|
gen.addLoadImmediate(Register.R0, 3);
|
||||||
|
gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test load from memory.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadFromMemory(Register.R0, 0);
|
||||||
|
gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test store to memory.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R1, 1234567890);
|
||||||
|
gen.addStoreToMemory(Register.R1, 12);
|
||||||
|
gen.addLoadFromMemory(Register.R0, 12);
|
||||||
|
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test filter age pre-filled memory.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
|
||||||
|
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen, new byte[MIN_PKT_SIZE], 1234567890);
|
||||||
|
|
||||||
|
// Test packet size pre-filled memory.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
|
||||||
|
gen.addJumpIfR0Equals(MIN_PKT_SIZE, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test IPv4 header size pre-filled memory.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
|
||||||
|
gen.addJumpIfR0Equals(20, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x45}, 0);
|
||||||
|
|
||||||
|
// Test not.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1234567890);
|
||||||
|
gen.addNot(Register.R0);
|
||||||
|
gen.addJumpIfR0Equals(~1234567890, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test negate.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1234567890);
|
||||||
|
gen.addNeg(Register.R0);
|
||||||
|
gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test move.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R1, 1234567890);
|
||||||
|
gen.addMove(Register.R0);
|
||||||
|
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1234567890);
|
||||||
|
gen.addMove(Register.R1);
|
||||||
|
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test swap.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R1, 1234567890);
|
||||||
|
gen.addSwap();
|
||||||
|
gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1234567890);
|
||||||
|
gen.addSwap();
|
||||||
|
gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen);
|
||||||
|
|
||||||
|
// Test jump if bytes not equal.
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1);
|
||||||
|
gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
|
||||||
|
program = gen.generate();
|
||||||
|
assertEquals(6, program.length);
|
||||||
|
assertEquals((13 << 3) | (1 << 1) | 0, program[0]);
|
||||||
|
assertEquals(1, program[1]);
|
||||||
|
assertEquals(((20 << 3) | (1 << 1) | 0) - 256, program[2]);
|
||||||
|
assertEquals(1, program[3]);
|
||||||
|
assertEquals(1, program[4]);
|
||||||
|
assertEquals(123, program[5]);
|
||||||
|
assertDrop(program, new byte[MIN_PKT_SIZE], 0);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1);
|
||||||
|
gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
|
||||||
|
byte[] packet123 = new byte[]{0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
||||||
|
assertPass(gen, packet123, 0);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
|
||||||
|
assertDrop(gen, packet123, 0);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1);
|
||||||
|
gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
|
||||||
|
byte[] packet12345 = new byte[]{0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
|
||||||
|
assertDrop(gen, packet12345, 0);
|
||||||
|
gen = new ApfGenerator();
|
||||||
|
gen.addLoadImmediate(Register.R0, 1);
|
||||||
|
gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL);
|
||||||
|
assertPass(gen, packet12345, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate some BPF programs, translate them to APF, then run APF and BPF programs
|
||||||
|
* over packet traces and verify both programs filter out the same packets.
|
||||||
|
*/
|
||||||
|
@LargeTest
|
||||||
|
public void testApfAgainstBpf() throws Exception {
|
||||||
|
String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
|
||||||
|
"arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
|
||||||
|
"arp or icmp6 or portrange 53-54", "portrange 53-54 or portrange 100-50000",
|
||||||
|
"tcp[tcpflags] & (tcp-ack|tcp-fin) != 0 and (ip[2:2] > 57 or icmp)" };
|
||||||
|
String pcap_filename = stageFile(R.raw.apf);
|
||||||
|
for (String tcpdump_filter : tcpdump_filters) {
|
||||||
|
byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter));
|
||||||
|
assertTrue("Failed to match for filter: " + tcpdump_filter,
|
||||||
|
compareBpfApf(tcpdump_filter, pcap_filename, apf_program));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stage a file for testing, i.e. make it native accessible. Given a resource ID,
|
||||||
|
* copy that resource into the app's data directory and return the path to it.
|
||||||
|
*/
|
||||||
|
private String stageFile(int rawId) throws Exception {
|
||||||
|
File file = new File(getContext().getFilesDir(), "staged_file");
|
||||||
|
new File(file.getParent()).mkdirs();
|
||||||
|
InputStream in = null;
|
||||||
|
OutputStream out = null;
|
||||||
|
try {
|
||||||
|
in = getContext().getResources().openRawResource(rawId);
|
||||||
|
out = new FileOutputStream(file);
|
||||||
|
Streams.copy(in, out);
|
||||||
|
} finally {
|
||||||
|
if (in != null) in.close();
|
||||||
|
if (out != null) out.close();
|
||||||
|
}
|
||||||
|
return file.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call the APF interpreter the run {@code program} on {@code packet} pretending the
|
||||||
|
* filter was installed {@code filter_age} seconds ago.
|
||||||
|
*/
|
||||||
|
private native static int apfSimulate(byte[] program, byte[] packet, int filter_age);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF
|
||||||
|
* prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d".
|
||||||
|
*/
|
||||||
|
private native static String compileToBpf(String filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open packet capture file {@code pcap_filename} and filter the packets using tcpdump
|
||||||
|
* human-readable filter (e.g. "icmp" or "tcp port 54") compiled to a BPF program and
|
||||||
|
* at the same time using APF program {@code apf_program}. Return {@code true} if
|
||||||
|
* both APF and BPF programs filter out exactly the same packets.
|
||||||
|
*/
|
||||||
|
private native static boolean compareBpfApf(String filter, String pcap_filename,
|
||||||
|
byte[] apf_program);
|
||||||
|
}
|
327
services/tests/servicestests/src/com/android/server/Bpf2Apf.java
Normal file
327
services/tests/servicestests/src/com/android/server/Bpf2Apf.java
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.server;
|
||||||
|
|
||||||
|
import android.net.apf.ApfGenerator;
|
||||||
|
import android.net.apf.ApfGenerator.IllegalInstructionException;
|
||||||
|
import android.net.apf.ApfGenerator.Register;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BPF to APF translator.
|
||||||
|
*
|
||||||
|
* Note: This is for testing purposes only and is not guaranteed to support
|
||||||
|
* translation of all BPF programs.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
* javac net/java/android/net/apf/ApfGenerator.java \
|
||||||
|
* tests/servicestests/src/com/android/server/Bpf2Apf.java
|
||||||
|
* sudo tcpdump -i em1 -d icmp | java -classpath tests/servicestests/src:net/java \
|
||||||
|
* com.android.server.Bpf2Apf
|
||||||
|
*/
|
||||||
|
public class Bpf2Apf {
|
||||||
|
private static int parseImm(String line, String arg) {
|
||||||
|
if (!arg.startsWith("#0x")) {
|
||||||
|
throw new IllegalArgumentException("Unhandled instruction: " + line);
|
||||||
|
}
|
||||||
|
final long val_long = Long.parseLong(arg.substring(3), 16);
|
||||||
|
if (val_long < 0 || val_long > Long.parseLong("ffffffff", 16)) {
|
||||||
|
throw new IllegalArgumentException("Unhandled instruction: " + line);
|
||||||
|
}
|
||||||
|
return new Long((val_long << 32) >> 32).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a single line of "tcpdump -d" (human readable BPF program dump) {@code line} into
|
||||||
|
* APF instruction(s) and append them to {@code gen}. Here's an example line:
|
||||||
|
* (001) jeq #0x86dd jt 2 jf 7
|
||||||
|
*/
|
||||||
|
private static void convertLine(String line, ApfGenerator gen)
|
||||||
|
throws IllegalInstructionException {
|
||||||
|
if (line.indexOf("(") != 0 || line.indexOf(")") != 4 || line.indexOf(" ") != 5) {
|
||||||
|
throw new IllegalArgumentException("Unhandled instruction: " + line);
|
||||||
|
}
|
||||||
|
int label = Integer.parseInt(line.substring(1, 4));
|
||||||
|
gen.defineLabel(Integer.toString(label));
|
||||||
|
String opcode = line.substring(6, 10).trim();
|
||||||
|
String arg = line.substring(15, Math.min(32, line.length())).trim();
|
||||||
|
switch (opcode) {
|
||||||
|
case "ld":
|
||||||
|
case "ldh":
|
||||||
|
case "ldb":
|
||||||
|
case "ldx":
|
||||||
|
case "ldxb":
|
||||||
|
case "ldxh":
|
||||||
|
Register dest = opcode.contains("x") ? Register.R1 : Register.R0;
|
||||||
|
if (arg.equals("4*([14]&0xf)")) {
|
||||||
|
if (!opcode.equals("ldxb")) {
|
||||||
|
throw new IllegalArgumentException("Unhandled instruction: " + line);
|
||||||
|
}
|
||||||
|
gen.addLoadFromMemory(dest, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (arg.equals("#pktlen")) {
|
||||||
|
if (!opcode.equals("ld")) {
|
||||||
|
throw new IllegalArgumentException("Unhandled instruction: " + line);
|
||||||
|
}
|
||||||
|
gen.addLoadFromMemory(dest, gen.PACKET_SIZE_MEMORY_SLOT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (arg.startsWith("#0x")) {
|
||||||
|
if (!opcode.equals("ld")) {
|
||||||
|
throw new IllegalArgumentException("Unhandled instruction: " + line);
|
||||||
|
}
|
||||||
|
gen.addLoadImmediate(dest, parseImm(line, arg));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (arg.startsWith("M[")) {
|
||||||
|
if (!opcode.startsWith("ld")) {
|
||||||
|
throw new IllegalArgumentException("Unhandled instruction: " + line);
|
||||||
|
}
|
||||||
|
int memory_slot = Integer.parseInt(arg.substring(2, arg.length() - 1));
|
||||||
|
if (memory_slot < 0 || memory_slot >= gen.MEMORY_SLOTS ||
|
||||||
|
// Disallow use of pre-filled slots as BPF programs might
|
||||||
|
// wrongfully assume they're initialized to 0.
|
||||||
|
(memory_slot >= gen.FIRST_PREFILLED_MEMORY_SLOT &&
|
||||||
|
memory_slot <= gen.LAST_PREFILLED_MEMORY_SLOT)) {
|
||||||
|
throw new IllegalArgumentException("Unhandled instruction: " + line);
|
||||||
|
}
|
||||||
|
gen.addLoadFromMemory(dest, memory_slot);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (arg.startsWith("[x + ")) {
|
||||||
|
int offset = Integer.parseInt(arg.substring(5, arg.length() - 1));
|
||||||
|
switch (opcode) {
|
||||||
|
case "ld":
|
||||||
|
case "ldx":
|
||||||
|
gen.addLoad32Indexed(dest, offset);
|
||||||
|
break;
|
||||||
|
case "ldh":
|
||||||
|
case "ldxh":
|
||||||
|
gen.addLoad16Indexed(dest, offset);
|
||||||
|
break;
|
||||||
|
case "ldb":
|
||||||
|
case "ldxb":
|
||||||
|
gen.addLoad8Indexed(dest, offset);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int offset = Integer.parseInt(arg.substring(1, arg.length() - 1));
|
||||||
|
switch (opcode) {
|
||||||
|
case "ld":
|
||||||
|
case "ldx":
|
||||||
|
gen.addLoad32(dest, offset);
|
||||||
|
break;
|
||||||
|
case "ldh":
|
||||||
|
case "ldxh":
|
||||||
|
gen.addLoad16(dest, offset);
|
||||||
|
break;
|
||||||
|
case "ldb":
|
||||||
|
case "ldxb":
|
||||||
|
gen.addLoad8(dest, offset);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "st":
|
||||||
|
case "stx":
|
||||||
|
Register src = opcode.contains("x") ? Register.R1 : Register.R0;
|
||||||
|
if (!arg.startsWith("M[")) {
|
||||||
|
throw new IllegalArgumentException("Unhandled instruction: " + line);
|
||||||
|
}
|
||||||
|
int memory_slot = Integer.parseInt(arg.substring(2, arg.length() - 1));
|
||||||
|
if (memory_slot < 0 || memory_slot >= gen.MEMORY_SLOTS ||
|
||||||
|
// Disallow overwriting pre-filled slots
|
||||||
|
(memory_slot >= gen.FIRST_PREFILLED_MEMORY_SLOT &&
|
||||||
|
memory_slot <= gen.LAST_PREFILLED_MEMORY_SLOT)) {
|
||||||
|
throw new IllegalArgumentException("Unhandled instruction: " + line);
|
||||||
|
}
|
||||||
|
gen.addStoreToMemory(src, memory_slot);
|
||||||
|
break;
|
||||||
|
case "add":
|
||||||
|
case "and":
|
||||||
|
case "or":
|
||||||
|
case "sub":
|
||||||
|
if (arg.equals("x")) {
|
||||||
|
switch(opcode) {
|
||||||
|
case "add":
|
||||||
|
gen.addAddR1();
|
||||||
|
break;
|
||||||
|
case "and":
|
||||||
|
gen.addAndR1();
|
||||||
|
break;
|
||||||
|
case "or":
|
||||||
|
gen.addOrR1();
|
||||||
|
break;
|
||||||
|
case "sub":
|
||||||
|
gen.addNeg(Register.R1);
|
||||||
|
gen.addAddR1();
|
||||||
|
gen.addNeg(Register.R1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int imm = parseImm(line, arg);
|
||||||
|
switch(opcode) {
|
||||||
|
case "add":
|
||||||
|
gen.addAdd(imm);
|
||||||
|
break;
|
||||||
|
case "and":
|
||||||
|
gen.addAnd(imm);
|
||||||
|
break;
|
||||||
|
case "or":
|
||||||
|
gen.addOr(imm);
|
||||||
|
break;
|
||||||
|
case "sub":
|
||||||
|
gen.addAdd(-imm);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "jeq":
|
||||||
|
case "jset":
|
||||||
|
case "jgt":
|
||||||
|
case "jge":
|
||||||
|
int val = 0;
|
||||||
|
boolean reg_compare;
|
||||||
|
if (arg.startsWith("x")) {
|
||||||
|
reg_compare = true;
|
||||||
|
} else {
|
||||||
|
reg_compare = false;
|
||||||
|
val = parseImm(line, arg);
|
||||||
|
}
|
||||||
|
int jt_offset = line.indexOf("jt");
|
||||||
|
int jf_offset = line.indexOf("jf");
|
||||||
|
String true_label = line.substring(jt_offset + 2, jf_offset).trim();
|
||||||
|
String false_label = line.substring(jf_offset + 2).trim();
|
||||||
|
boolean true_label_is_fallthrough = Integer.parseInt(true_label) == label + 1;
|
||||||
|
boolean false_label_is_fallthrough = Integer.parseInt(false_label) == label + 1;
|
||||||
|
if (true_label_is_fallthrough && false_label_is_fallthrough)
|
||||||
|
break;
|
||||||
|
switch (opcode) {
|
||||||
|
case "jeq":
|
||||||
|
if (!true_label_is_fallthrough) {
|
||||||
|
if (reg_compare) {
|
||||||
|
gen.addJumpIfR0EqualsR1(true_label);
|
||||||
|
} else {
|
||||||
|
gen.addJumpIfR0Equals(val, true_label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!false_label_is_fallthrough) {
|
||||||
|
if (!true_label_is_fallthrough) {
|
||||||
|
gen.addJump(false_label);
|
||||||
|
} else if (reg_compare) {
|
||||||
|
gen.addJumpIfR0NotEqualsR1(false_label);
|
||||||
|
} else {
|
||||||
|
gen.addJumpIfR0NotEquals(val, false_label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "jset":
|
||||||
|
if (reg_compare) {
|
||||||
|
gen.addJumpIfR0AnyBitsSetR1(true_label);
|
||||||
|
} else {
|
||||||
|
gen.addJumpIfR0AnyBitsSet(val, true_label);
|
||||||
|
}
|
||||||
|
if (!false_label_is_fallthrough) {
|
||||||
|
gen.addJump(false_label);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "jgt":
|
||||||
|
if (!true_label_is_fallthrough ||
|
||||||
|
// We have no less-than-or-equal-to register to register
|
||||||
|
// comparison instruction, so in this case we'll jump
|
||||||
|
// around an unconditional jump.
|
||||||
|
(!false_label_is_fallthrough && reg_compare)) {
|
||||||
|
if (reg_compare) {
|
||||||
|
gen.addJumpIfR0GreaterThanR1(true_label);
|
||||||
|
} else {
|
||||||
|
gen.addJumpIfR0GreaterThan(val, true_label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!false_label_is_fallthrough) {
|
||||||
|
if (!true_label_is_fallthrough || reg_compare) {
|
||||||
|
gen.addJump(false_label);
|
||||||
|
} else {
|
||||||
|
gen.addJumpIfR0LessThan(val + 1, false_label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "jge":
|
||||||
|
if (!false_label_is_fallthrough ||
|
||||||
|
// We have no greater-than-or-equal-to register to register
|
||||||
|
// comparison instruction, so in this case we'll jump
|
||||||
|
// around an unconditional jump.
|
||||||
|
(!true_label_is_fallthrough && reg_compare)) {
|
||||||
|
if (reg_compare) {
|
||||||
|
gen.addJumpIfR0LessThanR1(false_label);
|
||||||
|
} else {
|
||||||
|
gen.addJumpIfR0LessThan(val, false_label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!true_label_is_fallthrough) {
|
||||||
|
if (!false_label_is_fallthrough || reg_compare) {
|
||||||
|
gen.addJump(true_label);
|
||||||
|
} else {
|
||||||
|
gen.addJumpIfR0GreaterThan(val - 1, true_label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "ret":
|
||||||
|
if (arg.equals("#0")) {
|
||||||
|
gen.addJump(gen.DROP_LABEL);
|
||||||
|
} else {
|
||||||
|
gen.addJump(gen.PASS_LABEL);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "tax":
|
||||||
|
gen.addMove(Register.R1);
|
||||||
|
break;
|
||||||
|
case "txa":
|
||||||
|
gen.addMove(Register.R0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unhandled instruction: " + line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the output of "tcpdump -d" (human readable BPF program dump) {@code bpf} into an APF
|
||||||
|
* program and return it.
|
||||||
|
*/
|
||||||
|
public static byte[] convert(String bpf) throws IllegalInstructionException {
|
||||||
|
ApfGenerator gen = new ApfGenerator();
|
||||||
|
for (String line : bpf.split("\\n")) convertLine(line, gen);
|
||||||
|
return gen.generate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the output of "tcpdump -d" (human readable BPF program dump) piped in stdin into an
|
||||||
|
* APF program and output it via stdout.
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
|
||||||
|
String line = null;
|
||||||
|
StringBuilder responseData = new StringBuilder();
|
||||||
|
ApfGenerator gen = new ApfGenerator();
|
||||||
|
while ((line = in.readLine()) != null) convertLine(line, gen);
|
||||||
|
System.out.write(gen.generate());
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user