Updated ApfGenerator to support WRITE/EWRITE opcodes.

Added support for WRITE opcode, which writes 1, 2, or 4 bytes from an
immediate value to the output buffer. Added support for EWRITE opcode,
which writes 1, 2, or 4 bytes from a register to the output buffer.

Bug: 293811969
Test: TH
Change-Id: Ia644a80bfd0655296f178010ee1a5a941021afd6
diff --git a/src/android/net/apf/ApfGenerator.java b/src/android/net/apf/ApfGenerator.java
index 4b261d2..0460c83 100644
--- a/src/android/net/apf/ApfGenerator.java
+++ b/src/android/net/apf/ApfGenerator.java
@@ -63,7 +63,8 @@
         JNEBS(20), // Compare not equal byte sequence, e.g. "jnebs R0,5,label,0x1122334455"
         EXT(21),   // Followed by immediate indicating ExtendedOpcodes.
         LDDW(22),  // Load 4 bytes from data memory address (register + immediate): "lddw R0, [5]R1"
-        STDW(23);  // Store 4 bytes to data memory address (register + immediate): "stdw R0, [5]R1"
+        STDW(23),  // Store 4 bytes to data memory address (register + immediate): "stdw R0, [5]R1"
+        WRITE(24); // Write 1, 2 or 4 bytes imm to the output buffer, e.g. "WRITE 5"
 
         final int value;
 
@@ -81,7 +82,10 @@
         SWAP(34), // Swap, e.g. "swap R0,R1"
         MOVE(35),  // Move, e.g. "move R0,R1"
         ALLOC(36), // Allocate buffer, "e.g. ALLOC R0"
-        TRANS(37); // Transmit buffer, "e.g. TRANS R0"
+        TRANS(37), // Transmit buffer, "e.g. TRANS R0"
+        EWRITE1(38), // Write 1 byte from register to the output buffer, e.g. "EWRITE1 R0"
+        EWRITE2(39), // Write 2 bytes from register to the output buffer, e.g. "EWRITE2 R0"
+        EWRITE4(40); // Write 4 bytes from register to the output buffer, e.g. "EWRITE4 R0"
 
         final int value;
 
@@ -106,9 +110,13 @@
         public final int mValue;
 
         Immediate(int value, boolean signed) {
+            this(value, signed, calculateImmSize(value, signed));
+        }
+
+        Immediate(int value, boolean signed, byte size) {
             mValue = value;
             mSigned = signed;
-            mImmSize = calculateImmSize(value, signed);
+            mImmSize = size;
         }
     }
 
@@ -141,21 +149,25 @@
             this(opcode, Register.R0);
         }
 
-        void addImm(int imm, boolean signed) {
+        void addUnsignedImm(int imm) {
+            addImm(new Immediate(imm, false));
+        }
+
+        void addUnsignedImm(int imm, byte size) {
+            addImm(new Immediate(imm, false, size));
+        }
+
+        void addSignedImm(int imm) {
+            addImm(new Immediate(imm, true));
+        }
+
+        void addImm(Immediate imm) {
             if (mImms.size() == mMaxSupportedImmediates) {
                 throw new IllegalArgumentException(
                         String.format("Opcode: %d only support at max: %d imms", mOpcode,
                                 mMaxSupportedImmediates));
             }
-            mImms.add(new Immediate(imm, signed));
-        }
-
-        void addUnsignedImm(int imm) {
-            addImm(imm, false);
-        }
-
-        void addSignedImm(int imm) {
-            addImm(imm, true);
+            mImms.add(imm);
         }
 
         void setLabel(String label) throws IllegalInstructionException {
@@ -873,6 +885,57 @@
     }
 
     /**
+     * Add an instruction to the end of the program to write 1, 2 or 4 bytes value to output buffer.
+     *
+     * @param value the value to write
+     * @param size the size of the value
+     * @return the ApfGenerator object
+     * @throws IllegalInstructionException throws when size is not 1, 2 or 4
+     */
+    public ApfGenerator addWrite(int value, byte size) throws IllegalInstructionException {
+        requireApfVersion(5);
+        if (!(size == 1 || size == 2 || size == 4)) {
+            throw new IllegalInstructionException("length field must be 1, 2 or 4");
+        }
+        if (size < calculateImmSize(value, false)) {
+            throw new IllegalInstructionException(
+                    String.format("the value %d is unfit into size: %d", value, size));
+        }
+        Instruction instruction = new Instruction(Opcodes.WRITE);
+        instruction.addUnsignedImm(value, size);
+        addInstruction(instruction);
+        return this;
+    }
+
+    /**
+     * Add an instruction to the end of the program to write 1, 2 or 4 bytes value from register
+     * to output buffer.
+     *
+     * @param register the register contains the value to be written
+     * @param size the size of the value
+     * @return the ApfGenerator object
+     * @throws IllegalInstructionException throws when size is not 1, 2 or 4
+     */
+    public ApfGenerator addWrite(Register register, byte size)
+            throws IllegalInstructionException {
+        requireApfVersion(5);
+        if (!(size == 1 || size == 2 || size == 4)) {
+            throw new IllegalInstructionException(
+                    "length field must be 1, 2 or 4");
+        }
+        Instruction instruction = new Instruction(Opcodes.EXT, register);
+        if (size == 1) {
+            instruction.addUnsignedImm(ExtendedOpcodes.EWRITE1.value);
+        } else if (size == 2) {
+            instruction.addUnsignedImm(ExtendedOpcodes.EWRITE2.value);
+        } else {
+            instruction.addUnsignedImm(ExtendedOpcodes.EWRITE4.value);
+        }
+        addInstruction(instruction);
+        return this;
+    }
+
+    /**
      * Add an instruction to the end of the program to load 32 bits from the data memory into
      * {@code register}. The source address is computed by adding the signed immediate
      * @{code offset} to the other register.
diff --git a/tests/unit/src/android/net/apf/ApfV5Test.kt b/tests/unit/src/android/net/apf/ApfV5Test.kt
index 67589d7..d42396a 100644
--- a/tests/unit/src/android/net/apf/ApfV5Test.kt
+++ b/tests/unit/src/android/net/apf/ApfV5Test.kt
@@ -41,10 +41,41 @@
         program = gen.generate()
         assertContentEquals(byteArrayOf(encodeInstruction(21, 1, 1), 37), program)
         assertContentEquals(arrayOf("       0: trans r1"), ApfJniUtils.disassembleApf(program))
+
+        gen = ApfGenerator(MIN_APF_VERSION)
+        gen.addWrite(0x01, 1)
+        gen.addWrite(0x0102, 2)
+        gen.addWrite(0x01020304, 4)
+        program = gen.generate()
+        assertContentEquals(byteArrayOf(
+                encodeInstruction(24, 1, 0), 0x01,
+                encodeInstruction(24, 2, 0), 0x01, 0x02,
+                encodeInstruction(24, 4, 0), 0x01, 0x02, 0x03, 0x04
+        ), program)
+        assertContentEquals(arrayOf(
+                "       0: write 0x01",
+                "       2: write 0x0102",
+                "       5: write 0x01020304"), ApfJniUtils.disassembleApf(program))
+
+        gen = ApfGenerator(MIN_APF_VERSION)
+        gen.addWrite(ApfGenerator.Register.R0, 1)
+        gen.addWrite(ApfGenerator.Register.R0, 2)
+        gen.addWrite(ApfGenerator.Register.R0, 4)
+        program = gen.generate()
+        assertContentEquals(byteArrayOf(
+                encodeInstruction(21, 1, 0), 38,
+                encodeInstruction(21, 1, 0), 39,
+                encodeInstruction(21, 1, 0), 40
+        ), program)
+        assertContentEquals(arrayOf(
+                "       0: write r0, 1",
+                "       2: write r0, 2",
+                "       4: write r0, 4"), ApfJniUtils.disassembleApf(program))
     }
 
     private fun encodeInstruction(opcode: Int, immLength: Int, register: Int): Byte {
-        return opcode.shl(3).or(immLength.shl(1)).or(register).toByte()
+        val immLengthEncoding = if (immLength == 4) 3 else immLength
+        return opcode.shl(3).or(immLengthEncoding.shl(1)).or(register).toByte()
     }
 
     companion object {