summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-12-04 13:42:52 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-12-04 13:42:52 +0000
commitc334d10891fe859e09f145e83f44843db2f30382 (patch)
tree7fc098108cb9df82a1e8ab972d5a6cfeb604ae98
parentcda8ba24815dd4cb258f51ffc79f3325ca2a39f2 (diff)
parentd22e40555060c4064a78a8c4bf7ba392f138d46c (diff)
downloadapf-android14-mainline-sdkext-release.tar.gz
Snap for 11173240 from d22e40555060c4064a78a8c4bf7ba392f138d46c to mainline-sdkext-releaseaml_sdk_341410000android14-mainline-sdkext-release
Change-Id: Ia6fbc4f3680724dcd114fd2907f42e7a79bdc286
-rw-r--r--Android.bp14
-rwxr-xr-xapf_counter_decoder.py71
-rw-r--r--apf_disassembler.c5
-rw-r--r--apf_run.c131
-rw-r--r--disassembler.c398
-rw-r--r--disassembler.h24
-rw-r--r--testdata/one_ra_with_counters.output8
-rw-r--r--testdata/one_ra_with_counters_age_30.output8
-rw-r--r--testdata/one_ra_with_counters_age_600.output8
-rw-r--r--testdata/one_ra_with_counters_age_601.output8
-rw-r--r--v5/Android.bp29
-rw-r--r--v5/apf.h198
-rw-r--r--v5/apf_interpreter.c406
-rw-r--r--v5/apf_interpreter.h127
-rw-r--r--v5/test_buf_allocator.c50
-rw-r--r--v5/test_buf_allocator.h29
16 files changed, 1427 insertions, 87 deletions
diff --git a/Android.bp b/Android.bp
index dad6f38..a1abee1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -40,6 +40,15 @@ cc_library_static {
sdk_version: "24",
}
+cc_library_static {
+ name: "libapfdisassembler",
+ defaults: ["apf_defaults"],
+ srcs: [
+ "disassembler.c",
+ ],
+ sdk_version: "24",
+}
+
cc_binary_host {
name: "apf_disassembler",
defaults: ["apf_defaults"],
@@ -59,6 +68,8 @@ cc_binary_host {
"apf_run.c",
"apf_interpreter.c",
"disassembler.c",
+ "v5/apf_interpreter.c",
+ "v5/test_buf_allocator.c"
],
cflags: [
"-DAPF_TRACE_HOOK=apf_trace_hook",
@@ -85,6 +96,9 @@ sh_test_host {
enabled: false,
},
},
+ data_libs: [
+ "libc++",
+ ],
test_options: {
unit_test: true,
},
diff --git a/apf_counter_decoder.py b/apf_counter_decoder.py
new file mode 100755
index 0000000..fd4122a
--- /dev/null
+++ b/apf_counter_decoder.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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.
+#
+import argparse
+
+# The following list must be in sync with
+# https://cs.android.com/android/platform/superproject/+/master:packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java;l=139?q=ApfFilter.java
+Counter = (
+ "TOTAL_PACKETS",
+ "PASSED_ARP",
+ "PASSED_DHCP",
+ "PASSED_IPV4",
+ "PASSED_IPV6_NON_ICMP",
+ "PASSED_IPV4_UNICAST",
+ "PASSED_IPV6_ICMP",
+ "PASSED_IPV6_UNICAST_NON_ICMP",
+ "PASSED_ARP_NON_IPV4",
+ "PASSED_ARP_UNKNOWN",
+ "PASSED_ARP_UNICAST_REPLY",
+ "PASSED_NON_IP_UNICAST",
+ "PASSED_MDNS",
+ "DROPPED_ETH_BROADCAST",
+ "DROPPED_RA",
+ "DROPPED_GARP_REPLY",
+ "DROPPED_ARP_OTHER_HOST",
+ "DROPPED_IPV4_L2_BROADCAST",
+ "DROPPED_IPV4_BROADCAST_ADDR",
+ "DROPPED_IPV4_BROADCAST_NET",
+ "DROPPED_IPV4_MULTICAST",
+ "DROPPED_IPV6_ROUTER_SOLICITATION",
+ "DROPPED_IPV6_MULTICAST_NA",
+ "DROPPED_IPV6_MULTICAST",
+ "DROPPED_IPV6_MULTICAST_PING",
+ "DROPPED_IPV6_NON_ICMP_MULTICAST",
+ "DROPPED_802_3_FRAME",
+ "DROPPED_ETHERTYPE_BLACKLISTED",
+ "DROPPED_ARP_REPLY_SPA_NO_HOST",
+ "DROPPED_IPV4_KEEPALIVE_ACK",
+ "DROPPED_IPV6_KEEPALIVE_ACK",
+ "DROPPED_IPV4_NATT_KEEPALIVE",
+ "DROPPED_MDNS"
+)
+
+def main():
+ parser = argparse.ArgumentParser(description='Parse APF counter HEX string.')
+ parser.add_argument('hexstring')
+ args = parser.parse_args()
+ data_hexstring = args.hexstring
+ data_bytes = bytes.fromhex(data_hexstring)
+ data_bytes_len = len(data_bytes)
+ for i in range(len(Counter)):
+ cnt = int.from_bytes(data_bytes[data_bytes_len - 4 * (i + 1) :
+ data_bytes_len - 4 * i], byteorder = "big")
+ if cnt != 0:
+ print("{} : {}".format(Counter[i], cnt))
+
+if __name__ == "__main__":
+ main()
diff --git a/apf_disassembler.c b/apf_disassembler.c
index a7401f3..738079a 100644
--- a/apf_disassembler.c
+++ b/apf_disassembler.c
@@ -29,6 +29,7 @@
int main(void) {
uint32_t program_len = 0;
uint8_t program[10000];
+ char output_buffer[512];
// Read in hex program bytes
int byte;
@@ -37,6 +38,8 @@ int main(void) {
}
for (uint32_t pc = 0; pc < program_len;) {
- pc = apf_disassemble(program, program_len, pc);
+ pc = apf_disassemble(program, program_len, pc, output_buffer,
+ sizeof(output_buffer) / sizeof(output_buffer[0]));
+ printf("%s\n", output_buffer);
}
}
diff --git a/apf_run.c b/apf_run.c
index 38d28a2..d40172f 100644
--- a/apf_run.c
+++ b/apf_run.c
@@ -29,9 +29,50 @@
#include "disassembler.h"
#include "apf_interpreter.h"
+#include "v5/apf_interpreter.h"
+#include "v5/test_buf_allocator.h"
#define __unused __attribute__((unused))
+// The following list must be in sync with
+// https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java;l=125
+static const char* counter_name [] = {
+ "RESERVED_OOB",
+ "TOTAL_PACKETS",
+ "PASSED_ARP",
+ "PASSED_DHCP",
+ "PASSED_IPV4",
+ "PASSED_IPV6_NON_ICMP",
+ "PASSED_IPV4_UNICAST",
+ "PASSED_IPV6_ICMP",
+ "PASSED_IPV6_UNICAST_NON_ICMP",
+ "PASSED_ARP_NON_IPV4",
+ "PASSED_ARP_UNKNOWN",
+ "PASSED_ARP_UNICAST_REPLY",
+ "PASSED_NON_IP_UNICAST",
+ "PASSED_MDNS",
+ "DROPPED_ETH_BROADCAST",
+ "DROPPED_RA",
+ "DROPPED_GARP_REPLY",
+ "DROPPED_ARP_OTHER_HOST",
+ "DROPPED_IPV4_L2_BROADCAST",
+ "DROPPED_IPV4_BROADCAST_ADDR",
+ "DROPPED_IPV4_BROADCAST_NET",
+ "DROPPED_IPV4_MULTICAST",
+ "DROPPED_IPV6_ROUTER_SOLICITATION",
+ "DROPPED_IPV6_MULTICAST_NA",
+ "DROPPED_IPV6_MULTICAST",
+ "DROPPED_IPV6_MULTICAST_PING",
+ "DROPPED_IPV6_NON_ICMP_MULTICAST",
+ "DROPPED_802_3_FRAME",
+ "DROPPED_ETHERTYPE_BLACKLISTED",
+ "DROPPED_ARP_REPLY_SPA_NO_HOST",
+ "DROPPED_IPV4_KEEPALIVE_ACK",
+ "DROPPED_IPV6_KEEPALIVE_ACK",
+ "DROPPED_IPV4_NATT_KEEPALIVE",
+ "DROPPED_MDNS"
+};
+
enum {
OPT_PROGRAM,
OPT_PACKET,
@@ -39,6 +80,7 @@ enum {
OPT_DATA,
OPT_AGE,
OPT_TRACE,
+ OPT_V6,
};
const struct option long_options[] = {{"program", 1, NULL, OPT_PROGRAM},
@@ -47,9 +89,13 @@ const struct option long_options[] = {{"program", 1, NULL, OPT_PROGRAM},
{"data", 1, NULL, OPT_DATA},
{"age", 1, NULL, OPT_AGE},
{"trace", 0, NULL, OPT_TRACE},
+ {"v6", 0, NULL, OPT_V6},
{"help", 0, NULL, 'h'},
+ {"cnt", 0, NULL, 'c'},
{NULL, 0, NULL, 0}};
+const int COUNTER_SIZE = 4;
+
// Parses hex in "input". Allocates and fills "*output" with parsed bytes.
// Returns length in bytes of "*output".
size_t parse_hex(const char* input, uint8_t** output) {
@@ -82,6 +128,28 @@ void print_hex(const uint8_t* input, int len) {
}
}
+uint32_t get_counter_value(const uint8_t* data, int data_len, int neg_offset) {
+ if (neg_offset > -COUNTER_SIZE || neg_offset + data_len < 0) {
+ return 0;
+ }
+ uint32_t value = 0;
+ for (int i = 0; i < 4; ++i) {
+ value = value << 8 | data[data_len + neg_offset];
+ neg_offset++;
+ }
+ return value;
+}
+
+void print_counter(const uint8_t* data, int data_len) {
+ int counter_len = sizeof(counter_name) / sizeof(counter_name[0]);
+ for (int i = 0; i < counter_len; ++i) {
+ uint32_t value = get_counter_value(data, data_len, -COUNTER_SIZE * i);
+ if (value != 0) {
+ printf("%s : %d \n", counter_name[i], value);
+ }
+ }
+}
+
int tracing_enabled = 0;
void maybe_print_tracing_header() {
@@ -92,32 +160,49 @@ void maybe_print_tracing_header() {
}
+void print_transmitted_packet() {
+ printf("transmitted packet: ");
+ print_hex(apf_test_tx_packet, (int) apf_test_tx_packet_len);
+ printf("\n");
+}
+
// Process packet through APF filter
-void packet_handler(uint8_t* program, uint32_t program_len, uint32_t ram_len,
- const char* pkt, uint32_t filter_age) {
+void packet_handler(int use_apf_v6_interpreter, uint8_t* program,
+ uint32_t program_len, uint32_t ram_len, const char* pkt, uint32_t filter_age) {
uint8_t* packet;
uint32_t packet_len = parse_hex(pkt, &packet);
maybe_print_tracing_header();
- int ret = accept_packet(program, program_len, ram_len, packet, packet_len,
+ int ret;
+ if (use_apf_v6_interpreter) {
+ ret = apf_run(program, program_len, ram_len, packet, packet_len,
filter_age);
+ } else {
+ ret = accept_packet(program, program_len, ram_len, packet, packet_len,
+ filter_age);
+ }
printf("Packet %sed\n", ret ? "pass" : "dropp");
free(packet);
}
+static char output_buffer[512];
+
void apf_trace_hook(uint32_t pc, const uint32_t* regs, const uint8_t* program, uint32_t program_len,
const uint8_t* packet __unused, uint32_t packet_len __unused,
const uint32_t* memory __unused, uint32_t memory_len __unused) {
if (!tracing_enabled) return;
printf("%8" PRIx32 " %8" PRIx32 " ", regs[0], regs[1]);
- apf_disassemble(program, program_len, pc);
+ apf_disassemble(program, program_len, pc, output_buffer,
+ sizeof(output_buffer) / sizeof(output_buffer[0]));
+ printf("%s\n", output_buffer);
}
// Process pcap file through APF filter and generate output files
-void file_handler(uint8_t* program, uint32_t program_len, uint32_t ram_len, const char* filename,
+void file_handler(int use_apf_v6_interpreter, uint8_t* program,
+ uint32_t program_len, uint32_t ram_len, const char* filename,
uint32_t filter_age) {
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t *pcap;
@@ -147,8 +232,14 @@ void file_handler(uint8_t* program, uint32_t program_len, uint32_t ram_len, cons
while ((apf_packet = pcap_next(pcap, &apf_header)) != NULL) {
maybe_print_tracing_header();
- int result = accept_packet(program, program_len, ram_len, apf_packet,
- apf_header.len, filter_age);
+ int result;
+ if (use_apf_v6_interpreter) {
+ result = apf_run(program, program_len, ram_len, apf_packet,
+ apf_header.len, filter_age);
+ } else {
+ result = accept_packet(program, program_len, ram_len, apf_packet,
+ apf_header.len, filter_age);
+ }
if (!result){
drop++;
@@ -176,6 +267,8 @@ void print_usage(char* cmd) {
" --data Data memory contents, in hex.\n"
" --age Age of program in seconds (default: 0).\n"
" --trace Enable APF interpreter debug tracing\n"
+ " --v6 Use APF v6\n"
+ " -c, --cnt Print the APF counters\n"
" -h, --help Show this message.\n",
basename(cmd));
}
@@ -188,11 +281,13 @@ int main(int argc, char* argv[]) {
uint8_t* data = NULL;
uint32_t data_len = 0;
uint32_t filter_age = 0;
+ int print_counter_enabled = 0;
+ int use_apf_v6_interpreter = 0;
int opt;
char *endptr;
- while ((opt = getopt_long_only(argc, argv, "h", long_options, NULL)) != -1) {
+ while ((opt = getopt_long_only(argc, argv, "ch", long_options, NULL)) != -1) {
switch (opt) {
case OPT_PROGRAM:
program_len = parse_hex(optarg, &program);
@@ -245,10 +340,16 @@ int main(int argc, char* argv[]) {
case OPT_TRACE:
tracing_enabled = 1;
break;
+ case OPT_V6:
+ use_apf_v6_interpreter = 1;
+ break;
case 'h':
print_usage(argv[0]);
exit(0);
break;
+ case 'c':
+ print_counter_enabled = 1;
+ break;
default:
print_usage(argv[0]);
exit(1);
@@ -276,14 +377,24 @@ int main(int argc, char* argv[]) {
uint32_t ram_len = program_len + data_len;
if (filename)
- file_handler(program, program_len, ram_len, filename, filter_age);
+ file_handler(use_apf_v6_interpreter, program, program_len, ram_len,
+ filename, filter_age);
else
- packet_handler(program, program_len, ram_len, packet, filter_age);
+ packet_handler(use_apf_v6_interpreter, program, program_len, ram_len,
+ packet, filter_age);
if (data_len) {
printf("Data: ");
print_hex(program + program_len, data_len);
printf("\n");
+ if (print_counter_enabled) {
+ printf("APF packet counters: \n");
+ print_counter(program + program_len, data_len);
+ }
+ }
+
+ if (use_apf_v6_interpreter && apf_test_tx_packet_len != 0) {
+ print_transmitted_packet();
}
free(program);
diff --git a/disassembler.c b/disassembler.c
index 3b66265..06a164b 100644
--- a/disassembler.c
+++ b/disassembler.c
@@ -17,15 +17,18 @@
#include <stdint.h>
#include <stdio.h>
-#include "apf.h"
+#include "v5/apf.h"
// If "c" is of a signed type, generate a compile warning that gets promoted to an error.
// This makes bounds checking simpler because ">= 0" can be avoided. Otherwise adding
// superfluous ">= 0" with unsigned expressions generates compile warnings.
#define ENFORCE_UNSIGNED(c) ((c)==(uint32_t)(c))
-static void print_opcode(const char* opcode) {
- printf("%-6s", opcode);
+static int print_opcode(const char* opcode, char* output_buffer,
+ int output_buffer_len, int offset) {
+ int ret = snprintf(output_buffer + offset, output_buffer_len - offset,
+ "%-6s", opcode);
+ return ret;
}
// Mapping from opcode number to opcode name.
@@ -52,34 +55,68 @@ static const char* opcode_names [] = {
[JNEBS_OPCODE] = "jnebs",
[LDDW_OPCODE] = "lddw",
[STDW_OPCODE] = "stdw",
+ [WRITE_OPCODE] = "write",
};
-static void print_jump_target(uint32_t target, uint32_t program_len) {
+static int print_jump_target(uint32_t target, uint32_t program_len,
+ char* output_buffer, int output_buffer_len,
+ int offset) {
+ int ret;
if (target == program_len) {
- printf("PASS");
+ ret = snprintf(output_buffer + offset, output_buffer_len - offset,
+ "PASS");
} else if (target == program_len + 1) {
- printf("DROP");
+ ret = snprintf(output_buffer + offset, output_buffer_len - offset,
+ "DROP");
} else {
- printf("%u", target);
+ ret = snprintf(output_buffer + offset, output_buffer_len - offset,
+ "%u", target);
}
+ return ret;
}
-uint32_t apf_disassemble(const uint8_t* program, uint32_t program_len, uint32_t pc) {
- printf("%8u: ", pc);
+uint32_t apf_disassemble(const uint8_t* program, uint32_t program_len,
+ uint32_t pc, char* output_buffer,
+ int output_buffer_len) {
+ if (pc > program_len + 1) {
+ fprintf(stderr, "pc is overflow: pc %d, program_len: %d", pc,
+ program_len);
+ return pc;
+ }
+#define ASSERT_RET_INBOUND(x) \
+ if ((x) < 0 || (x) >= (output_buffer_len - offset)) return pc + 2
+
+ int offset = 0;
+ int ret;
+ ret = snprintf(output_buffer + offset, output_buffer_len - offset,
+ "%8u: ", pc);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
if (pc == program_len) {
- printf("PASS\n");
+ ret = snprintf(output_buffer + offset, output_buffer_len - offset,
+ "PASS");
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
return ++pc;
}
if (pc == program_len + 1) {
- printf("DROP\n");
+ ret = snprintf(output_buffer + offset, output_buffer_len - offset,
+ "DROP");
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
return ++pc;
}
const uint8_t bytecode = program[pc++];
const uint32_t opcode = EXTRACT_OPCODE(bytecode);
-#define PRINT_OPCODE() print_opcode(opcode_names[opcode])
+#define PRINT_OPCODE() \
+ print_opcode(opcode_names[opcode], output_buffer, output_buffer_len, offset)
+#define DECODE_IMM(value, length) \
+ for (uint32_t i = 0; i < (length) && pc < program_len; i++) \
+ value = (value << 8) | program[pc++]
+
const uint32_t reg_num = EXTRACT_REGISTER(bytecode);
// All instructions have immediate fields, so load them now.
const uint32_t len_field = EXTRACT_IMM_LENGTH(bytecode);
@@ -87,8 +124,7 @@ uint32_t apf_disassemble(const uint8_t* program, uint32_t program_len, uint32_t
int32_t signed_imm = 0;
if (len_field != 0) {
const uint32_t imm_len = 1 << (len_field - 1);
- for (uint32_t i = 0; i < imm_len && pc < program_len; i++)
- imm = (imm << 8) | program[pc++];
+ DECODE_IMM(imm, imm_len);
// Sign extend imm into signed_imm.
signed_imm = imm << ((4 - imm_len) * 8);
signed_imm >>= (4 - imm_len) * 8;
@@ -97,18 +133,42 @@ uint32_t apf_disassemble(const uint8_t* program, uint32_t program_len, uint32_t
case LDB_OPCODE:
case LDH_OPCODE:
case LDW_OPCODE:
- PRINT_OPCODE();
- printf("r%d, [%u]", reg_num, imm);
+ ret = PRINT_OPCODE();
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ ret = snprintf(output_buffer + offset, output_buffer_len - offset,
+ "r%d, [%u]", reg_num, imm);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
break;
case LDBX_OPCODE:
case LDHX_OPCODE:
case LDWX_OPCODE:
- PRINT_OPCODE();
- printf("r%d, [r1+%u]", reg_num, imm);
+ ret = PRINT_OPCODE();
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ if (imm) {
+ ret =
+ snprintf(output_buffer + offset, output_buffer_len - offset,
+ "r%d, [r1+%u]", reg_num, imm);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ } else {
+ ret =
+ snprintf(output_buffer + offset, output_buffer_len - offset,
+ "r%d, [r1]", reg_num);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ }
break;
case JMP_OPCODE:
- PRINT_OPCODE();
- print_jump_target(pc + imm, program_len);
+ ret = PRINT_OPCODE();
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ ret = print_jump_target(pc + imm, program_len, output_buffer,
+ output_buffer_len, offset);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
break;
case JEQ_OPCODE:
case JNE_OPCODE:
@@ -116,54 +176,106 @@ uint32_t apf_disassemble(const uint8_t* program, uint32_t program_len, uint32_t
case JLT_OPCODE:
case JSET_OPCODE:
case JNEBS_OPCODE: {
- PRINT_OPCODE();
- printf("r0, ");
+ ret = PRINT_OPCODE();
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ ret = snprintf(output_buffer + offset, output_buffer_len - offset,
+ "r0, ");
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
// Load second immediate field.
uint32_t cmp_imm = 0;
if (reg_num == 1) {
- printf("r1, ");
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "r1, ");
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
} else if (len_field == 0) {
- printf("0, ");
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "0, ");
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
} else {
- uint32_t cmp_imm_len = 1 << (len_field - 1);
- uint32_t i;
- for (i = 0; i < cmp_imm_len && pc < program_len; i++)
- cmp_imm = (cmp_imm << 8) | program[pc++];
- printf("0x%x, ", cmp_imm);
+ DECODE_IMM(cmp_imm, 1 << (len_field - 1));
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "0x%x, ", cmp_imm);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
}
if (opcode == JNEBS_OPCODE) {
- print_jump_target(pc + imm + cmp_imm, program_len);
- printf(", ");
- while (cmp_imm--)
- printf("%02x", program[pc++]);
+ ret = print_jump_target(pc + imm + cmp_imm, program_len,
+ output_buffer, output_buffer_len, offset);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, ", ");
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ while (cmp_imm--) {
+ uint8_t byte = program[pc++];
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "%02x", byte);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ }
} else {
- print_jump_target(pc + imm, program_len);
+ ret = print_jump_target(pc + imm, program_len, output_buffer,
+ output_buffer_len, offset);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
}
break;
}
- case ADD_OPCODE:
case SH_OPCODE:
- PRINT_OPCODE();
+ ret = PRINT_OPCODE();
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
if (reg_num) {
- printf("r0, r1");
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "r0, r1");
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
} else {
- printf("r0, %d", signed_imm);
+ ret =
+ snprintf(output_buffer + offset, output_buffer_len - offset,
+ "r0, %d", signed_imm);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
}
break;
+ case ADD_OPCODE:
case MUL_OPCODE:
case DIV_OPCODE:
case AND_OPCODE:
case OR_OPCODE:
- PRINT_OPCODE();
+ ret = PRINT_OPCODE();
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
if (reg_num) {
- printf("r0, r1");
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "r0, r1");
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ } else if (!imm && opcode == DIV_OPCODE) {
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "pass (div 0)");
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
} else {
- printf("r0, %u", imm);
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "r0, %u", imm);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
}
break;
case LI_OPCODE:
- PRINT_OPCODE();
- printf("r%d, %d", reg_num, signed_imm);
+ ret = PRINT_OPCODE();
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ ret = snprintf(output_buffer + offset, output_buffer_len - offset,
+ "r%d, %d", reg_num, signed_imm);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
break;
case EXT_OPCODE:
if (
@@ -175,43 +287,211 @@ uint32_t apf_disassemble(const uint8_t* program, uint32_t program_len, uint32_t
imm >= LDM_EXT_OPCODE &&
#endif
imm < (LDM_EXT_OPCODE + MEMORY_ITEMS)) {
- print_opcode("ldm");
- printf("r%d, m[%u]", reg_num, imm - LDM_EXT_OPCODE);
+ ret = print_opcode("ldm", output_buffer, output_buffer_len,
+ offset);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ ret =
+ snprintf(output_buffer + offset, output_buffer_len - offset,
+ "r%d, m[%u]", reg_num, imm - LDM_EXT_OPCODE);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
} else if (imm >= STM_EXT_OPCODE && imm < (STM_EXT_OPCODE + MEMORY_ITEMS)) {
- print_opcode("stm");
- printf("r%d, m[%u]", reg_num, imm - STM_EXT_OPCODE);
+ ret = print_opcode("stm", output_buffer, output_buffer_len,
+ offset);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ ret =
+ snprintf(output_buffer + offset, output_buffer_len - offset,
+ "r%d, m[%u]", reg_num, imm - STM_EXT_OPCODE);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
} else switch (imm) {
case NOT_EXT_OPCODE:
- print_opcode("not");
- printf("r%d", reg_num);
+ ret = print_opcode("not", output_buffer,
+ output_buffer_len, offset);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "r%d",
+ reg_num);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
break;
case NEG_EXT_OPCODE:
- print_opcode("neg");
- printf("r%d", reg_num);
+ ret = print_opcode("neg", output_buffer, output_buffer_len,
+ offset);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "r%d", reg_num);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
break;
case SWAP_EXT_OPCODE:
- print_opcode("swap");
+ ret = print_opcode("swap", output_buffer, output_buffer_len,
+ offset);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
break;
case MOV_EXT_OPCODE:
- print_opcode("mov");
- printf("r%d, r%d", reg_num, reg_num ^ 1);
+ ret = print_opcode("mov", output_buffer, output_buffer_len,
+ offset);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "r%d, r%d",
+ reg_num, reg_num ^ 1);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ break;
+ case ALLOC_EXT_OPCODE:
+ ret = print_opcode("alloc", output_buffer,
+ output_buffer_len, offset);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ ret =
+ snprintf(output_buffer + offset,
+ output_buffer_len - offset, "r%d", reg_num);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ break;
+ case TRANS_EXT_OPCODE:
+ ret = print_opcode("trans", output_buffer,
+ output_buffer_len, offset);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ ret =
+ snprintf(output_buffer + offset,
+ output_buffer_len - offset, "r%d", reg_num);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ break;
+ case EWRITE1_EXT_OPCODE:
+ case EWRITE2_EXT_OPCODE:
+ case EWRITE4_EXT_OPCODE: {
+ ret = print_opcode("write", output_buffer,
+ output_buffer_len, offset);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "r%d, %d",
+ reg_num, 1 << (imm - EWRITE1_EXT_OPCODE));
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ break;
+ }
+ case EDATACOPY:
+ case EPKTCOPY: {
+ if (imm == EPKTCOPY) {
+ ret = print_opcode("pcopy", output_buffer,
+ output_buffer_len, offset);
+ } else {
+ ret = print_opcode("dcopy", output_buffer,
+ output_buffer_len, offset);
+ }
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ if (len_field > 0) {
+ const uint32_t imm_len = 1 << (len_field - 1);
+ uint32_t relative_offs = 0;
+ DECODE_IMM(relative_offs, imm_len);
+ uint32_t copy_len = 0;
+ DECODE_IMM(copy_len, 1);
+
+ ret = snprintf(
+ output_buffer + offset, output_buffer_len - offset,
+ "[r%u+%d], %d", reg_num, relative_offs, copy_len);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ }
break;
+ }
default:
- printf("unknown_ext %u", imm);
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "unknown_ext %u",
+ imm);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
break;
}
break;
case LDDW_OPCODE:
case STDW_OPCODE:
- PRINT_OPCODE();
- printf("r%u, [r%u+%d]", reg_num, reg_num ^ 1, signed_imm);
+ ret = PRINT_OPCODE();
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ if (signed_imm > 0) {
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "r%u, [r%u+%d]", reg_num,
+ reg_num ^ 1, signed_imm);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ } else if (signed_imm < 0) {
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "r%u, [r%u-%d]",
+ reg_num, reg_num ^ 1, -signed_imm);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ } else {
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "r%u, [r%u]", reg_num,
+ reg_num ^ 1);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ }
break;
+ case WRITE_OPCODE: {
+ ret = PRINT_OPCODE();
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ uint32_t write_len = 1 << (len_field - 1);
+ if (write_len > 0) {
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "0x");
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ }
+ for (uint32_t i = 0; i < write_len; ++i) {
+ uint8_t byte =
+ (uint8_t) ((imm >> (write_len - 1 - i) * 8) & 0xff);
+ ret = snprintf(output_buffer + offset,
+ output_buffer_len - offset, "%02x", byte);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ }
+ break;
+ }
+ case MEMCOPY_OPCODE: {
+ if (reg_num == 0) {
+ ret = print_opcode("pcopy", output_buffer, output_buffer_len,
+ offset);
+ } else {
+ ret = print_opcode("dcopy", output_buffer, output_buffer_len,
+ offset);
+ }
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ if (len_field > 0) {
+ uint32_t src_offs = imm;
+ uint32_t copy_len = 0;
+ DECODE_IMM(copy_len, 1);
+ ret =
+ snprintf(output_buffer + offset, output_buffer_len - offset,
+ "%d, %d", src_offs, copy_len);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
+ }
+ break;
+ }
// Unknown opcode
default:
- printf("unknown %u", opcode);
+ ret = snprintf(output_buffer + offset, output_buffer_len - offset,
+ "unknown %u", opcode);
+ ASSERT_RET_INBOUND(ret);
+ offset += ret;
break;
}
- printf("\n");
return pc;
}
diff --git a/disassembler.h b/disassembler.h
index c13320e..cf10166 100644
--- a/disassembler.h
+++ b/disassembler.h
@@ -18,4 +18,26 @@
#include <stdint.h>
-uint32_t apf_disassemble(const uint8_t* program, uint32_t program_len, uint32_t pc);
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Disassembles a APF program into a human-readable format.
+ *
+ * @param program the program bytecode.
+ * @param program_len the length of the program bytecode.
+ * @param pc The program counter which point to the current instruction.
+ * @param output_buffer A pointer to a buffer where the disassembled
+ * instruction will be stored.
+ * @param output_buffer_len the length of the output buffer.
+ *
+ * @return the program counter which point to the next instruction.
+ */
+uint32_t apf_disassemble(const uint8_t* program, uint32_t program_len,
+ uint32_t pc, char* output_buffer,
+ int output_buffer_len);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/testdata/one_ra_with_counters.output b/testdata/one_ra_with_counters.output
index 9da27ec..000c0bb 100644
--- a/testdata/one_ra_with_counters.output
+++ b/testdata/one_ra_with_counters.output
@@ -1,9 +1,9 @@
R0 R1 PC Instruction
-------------------------------------------------
0 0 0: li r1, -4
- 0 fffffffc 2: lddw r0, [r1+0]
+ 0 fffffffc 2: lddw r0, [r1]
29 fffffffc 3: add r0, 1
- 2a fffffffc 5: stdw r0, [r1+0]
+ 2a fffffffc 5: stdw r0, [r1]
2a fffffffc 6: ldh r0, [12]
86dd fffffffc 8: li r1, -104
86dd ffffff98 10: jlt r0, 0x600, 503
@@ -48,9 +48,9 @@
82 ffffffac 468: jnebs r0, 0x14, 495, 000000002a0079e10abc0e000000000000000000
82 ffffffac 491: li r1, -56
82 ffffffc8 493: jmp 503
- 82 ffffffc8 503: lddw r0, [r1+0]
+ 82 ffffffc8 503: lddw r0, [r1]
1b ffffffc8 504: add r0, 1
- 1c ffffffc8 506: stdw r0, [r1+0]
+ 1c ffffffc8 506: stdw r0, [r1]
1c ffffffc8 507: jmp DROP
1c ffffffc8 510: DROP
Packet dropped
diff --git a/testdata/one_ra_with_counters_age_30.output b/testdata/one_ra_with_counters_age_30.output
index 8797e74..bf0d6ed 100644
--- a/testdata/one_ra_with_counters_age_30.output
+++ b/testdata/one_ra_with_counters_age_30.output
@@ -1,9 +1,9 @@
R0 R1 PC Instruction
-------------------------------------------------
0 0 0: li r1, -4
- 0 fffffffc 2: lddw r0, [r1+0]
+ 0 fffffffc 2: lddw r0, [r1]
29 fffffffc 3: add r0, 1
- 2a fffffffc 5: stdw r0, [r1+0]
+ 2a fffffffc 5: stdw r0, [r1]
2a fffffffc 6: ldh r0, [12]
86dd fffffffc 8: li r1, -104
86dd ffffff98 10: jlt r0, 0x600, 503
@@ -48,9 +48,9 @@
82 ffffffac 468: jnebs r0, 0x14, 495, 000000002a0079e10abc0e000000000000000000
82 ffffffac 491: li r1, -56
82 ffffffc8 493: jmp 503
- 82 ffffffc8 503: lddw r0, [r1+0]
+ 82 ffffffc8 503: lddw r0, [r1]
1b ffffffc8 504: add r0, 1
- 1c ffffffc8 506: stdw r0, [r1+0]
+ 1c ffffffc8 506: stdw r0, [r1]
1c ffffffc8 507: jmp DROP
1c ffffffc8 510: DROP
Packet dropped
diff --git a/testdata/one_ra_with_counters_age_600.output b/testdata/one_ra_with_counters_age_600.output
index 33c7d4a..231cac7 100644
--- a/testdata/one_ra_with_counters_age_600.output
+++ b/testdata/one_ra_with_counters_age_600.output
@@ -1,9 +1,9 @@
R0 R1 PC Instruction
-------------------------------------------------
0 0 0: li r1, -4
- 0 fffffffc 2: lddw r0, [r1+0]
+ 0 fffffffc 2: lddw r0, [r1]
29 fffffffc 3: add r0, 1
- 2a fffffffc 5: stdw r0, [r1+0]
+ 2a fffffffc 5: stdw r0, [r1]
2a fffffffc 6: ldh r0, [12]
86dd fffffffc 8: li r1, -104
86dd ffffff98 10: jlt r0, 0x600, 503
@@ -48,9 +48,9 @@
82 ffffffac 468: jnebs r0, 0x14, 495, 000000002a0079e10abc0e000000000000000000
82 ffffffac 491: li r1, -56
82 ffffffc8 493: jmp 503
- 82 ffffffc8 503: lddw r0, [r1+0]
+ 82 ffffffc8 503: lddw r0, [r1]
1b ffffffc8 504: add r0, 1
- 1c ffffffc8 506: stdw r0, [r1+0]
+ 1c ffffffc8 506: stdw r0, [r1]
1c ffffffc8 507: jmp DROP
1c ffffffc8 510: DROP
Packet dropped
diff --git a/testdata/one_ra_with_counters_age_601.output b/testdata/one_ra_with_counters_age_601.output
index 4228eb5..b272afd 100644
--- a/testdata/one_ra_with_counters_age_601.output
+++ b/testdata/one_ra_with_counters_age_601.output
@@ -1,9 +1,9 @@
R0 R1 PC Instruction
-------------------------------------------------
0 0 0: li r1, -4
- 0 fffffffc 2: lddw r0, [r1+0]
+ 0 fffffffc 2: lddw r0, [r1]
29 fffffffc 3: add r0, 1
- 2a fffffffc 5: stdw r0, [r1+0]
+ 2a fffffffc 5: stdw r0, [r1]
2a fffffffc 6: ldh r0, [12]
86dd fffffffc 8: li r1, -104
86dd ffffff98 10: jlt r0, 0x600, 503
@@ -27,9 +27,9 @@
96 ffffffac 295: ldm r0, m[15]
259 ffffffac 297: jgt r0, 0x258, 495
259 ffffffac 495: li r1, -28
- 259 ffffffe4 497: lddw r0, [r1+0]
+ 259 ffffffe4 497: lddw r0, [r1]
0 ffffffe4 498: add r0, 1
- 1 ffffffe4 500: stdw r0, [r1+0]
+ 1 ffffffe4 500: stdw r0, [r1]
1 ffffffe4 501: jmp PASS
1 ffffffe4 509: PASS
Packet passed
diff --git a/v5/Android.bp b/v5/Android.bp
new file mode 100644
index 0000000..9dbfd9f
--- /dev/null
+++ b/v5/Android.bp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2023 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 {
+ default_applicable_licenses: ["hardware_google_apf_license"],
+}
+
+cc_library_static {
+ name: "libapf_v5",
+ defaults: ["apf_defaults"],
+ srcs: [
+ "apf_interpreter.c",
+ "test_buf_allocator.c"
+ ],
+ sdk_version: "24",
+}
diff --git a/v5/apf.h b/v5/apf.h
new file mode 100644
index 0000000..b0b34af
--- /dev/null
+++ b/v5/apf.h
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2023, 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.
+ */
+
+#ifndef ANDROID_APF_APF_H
+#define ANDROID_APF_APF_H
+
+// A brief overview of APF:
+//
+// APF machine is composed of:
+// 1. A read-only program consisting of bytecodes as described below.
+// 2. Two 32-bit registers, called R0 and R1.
+// 3. Sixteen 32-bit temporary memory slots (cleared between packets).
+// 4. A read-only packet.
+// The program is executed by the interpreter below and parses the packet
+// to determine if the application processor (AP) should be woken up to
+// handle the packet or if can be dropped.
+//
+// APF bytecode description:
+//
+// The APF interpreter uses big-endian byte order for loads from the packet
+// and for storing immediates in instructions.
+//
+// Each instruction starts with a byte composed of:
+// Top 5 bits form "opcode" field, see *_OPCODE defines below.
+// Next 2 bits form "size field", which indicate the length of an immediate
+// value which follows the first byte. Values in this field:
+// 0 => immediate value is 0 and no bytes follow.
+// 1 => immediate value is 1 byte big.
+// 2 => immediate value is 2 bytes big.
+// 3 => immediate value is 4 bytes big.
+// Bottom bit forms "register" field, which indicates which register this
+// instruction operates on.
+//
+// There are three main categories of instructions:
+// Load instructions
+// These instructions load byte(s) of the packet into a register.
+// They load either 1, 2 or 4 bytes, as determined by the "opcode" field.
+// They load into the register specified by the "register" field.
+// The immediate value that follows the first byte of the instruction is
+// the byte offset from the beginning of the packet to load from.
+// There are "indexing" loads which add the value in R1 to the byte offset
+// to load from. The "opcode" field determines which loads are "indexing".
+// Arithmetic instructions
+// These instructions perform simple operations, like addition, on register
+// values. The result of these instructions is always written into R0. One
+// argument of the arithmetic operation is R0's value. The other argument
+// of the arithmetic operation is determined by the "register" field:
+// If the "register" field is 0 then the immediate value following
+// the first byte of the instruction is used as the other argument
+// to the arithmetic operation.
+// If the "register" field is 1 then R1's value is used as the other
+// argument to the arithmetic operation.
+// Conditional jump instructions
+// These instructions compare register R0's value with another value, and if
+// the comparison succeeds, jump (i.e. adjust the program counter). The
+// immediate value that follows the first byte of the instruction
+// represents the jump target offset, i.e. the value added to the program
+// counter if the comparison succeeds. The other value compared is
+// determined by the "register" field:
+// If the "register" field is 0 then another immediate value
+// follows the jump target offset. This immediate value is of the
+// same size as the jump target offset, and represents the value
+// to compare against.
+// If the "register" field is 1 then register R1's value is
+// compared against.
+// The type of comparison (e.g. equal to, greater than etc) is determined
+// by the "opcode" field. The comparison interprets both values being
+// compared as unsigned values.
+//
+// Miscellaneous details:
+//
+// Pre-filled temporary memory slot values
+// When the APF program begins execution, three of the sixteen memory slots
+// are pre-filled by the interpreter with values that may be useful for
+// programs:
+// Slot #11 contains the size (in bytes) of the APF program.
+// Slot #12 contains the total size of the APF buffer (program + data).
+// Slot #13 is filled with the IPv4 header length. This value is calculated
+// by loading the first byte of the IPv4 header and taking the
+// bottom 4 bits and multiplying their value by 4. This value is
+// set to zero if the first 4 bits after the link layer header are
+// not 4, indicating not IPv4.
+// Slot #14 is filled with size of the packet in bytes, including the
+// link-layer header if any.
+// Slot #15 is filled with the filter age in seconds. This is the number of
+// seconds since the AP sent the program to the chipset. This may
+// be used by filters that should have a particular lifetime. For
+// example, it can be used to rate-limit particular packets to one
+// every N seconds.
+// Special jump targets:
+// When an APF program executes a jump to the byte immediately after the last
+// byte of the progam (i.e., one byte past the end of the program), this
+// signals the program has completed and determined the packet should be
+// passed to the AP.
+// When an APF program executes a jump two bytes past the end of the program,
+// this signals the program has completed and determined the packet should
+// be dropped.
+// Jump if byte sequence doesn't match:
+// This is a special instruction to facilitate matching long sequences of
+// bytes in the packet. Initially it is encoded like a conditional jump
+// instruction with two exceptions:
+// The first byte of the instruction is always followed by two immediate
+// fields: The first immediate field is the jump target offset like other
+// conditional jump instructions. The second immediate field specifies the
+// number of bytes to compare.
+// These two immediate fields are followed by a sequence of bytes. These
+// bytes are compared with the bytes in the packet starting from the
+// position specified by the value of the register specified by the
+// "register" field of the instruction.
+
+// Number of temporary memory slots, see ldm/stm instructions.
+#define MEMORY_ITEMS 16
+// Upon program execution, some temporary memory slots are prefilled:
+
+// Offset inside the output buffer where the next byte of output packet should
+// be written to.
+#define MEMORY_OFFSET_OUTPUT_BUFFER_OFFSET 10
+#define MEMORY_OFFSET_PROGRAM_SIZE 11 // Size of program (in bytes)
+#define MEMORY_OFFSET_DATA_SIZE 12 // Total size of program + data
+#define MEMORY_OFFSET_IPV4_HEADER_SIZE 13 // 4*([APF_FRAME_HEADER_SIZE]&15)
+#define MEMORY_OFFSET_PACKET_SIZE 14 // Size of packet in bytes.
+#define MEMORY_OFFSET_FILTER_AGE 15 // Age since filter installed in seconds.
+
+// Leave 0 opcode unused as it's a good indicator of accidental incorrect execution (e.g. data).
+#define LDB_OPCODE 1 // Load 1 byte from immediate offset, e.g. "ldb R0, [5]"
+#define LDH_OPCODE 2 // Load 2 bytes from immediate offset, e.g. "ldh R0, [5]"
+#define LDW_OPCODE 3 // Load 4 bytes from immediate offset, e.g. "ldw R0, [5]"
+#define LDBX_OPCODE 4 // Load 1 byte from immediate offset plus register, e.g. "ldbx R0, [5+R0]"
+#define LDHX_OPCODE 5 // Load 2 byte from immediate offset plus register, e.g. "ldhx R0, [5+R0]"
+#define LDWX_OPCODE 6 // Load 4 byte from immediate offset plus register, e.g. "ldwx R0, [5+R0]"
+#define ADD_OPCODE 7 // Add, e.g. "add R0,5"
+#define MUL_OPCODE 8 // Multiply, e.g. "mul R0,5"
+#define DIV_OPCODE 9 // Divide, e.g. "div R0,5"
+#define AND_OPCODE 10 // And, e.g. "and R0,5"
+#define OR_OPCODE 11 // Or, e.g. "or R0,5"
+#define SH_OPCODE 12 // Left shift, e.g. "sh R0, 5" or "sh R0, -5" (shifts right)
+#define LI_OPCODE 13 // Load signed immediate, e.g. "li R0,5"
+#define JMP_OPCODE 14 // Unconditional jump, e.g. "jmp label"
+#define JEQ_OPCODE 15 // Compare equal and branch, e.g. "jeq R0,5,label"
+#define JNE_OPCODE 16 // Compare not equal and branch, e.g. "jne R0,5,label"
+#define JGT_OPCODE 17 // Compare greater than and branch, e.g. "jgt R0,5,label"
+#define JLT_OPCODE 18 // Compare less than and branch, e.g. "jlt R0,5,label"
+#define JSET_OPCODE 19 // Compare any bits set and branch, e.g. "jset R0,5,label"
+#define JNEBS_OPCODE 20 // Compare not equal byte sequence, e.g. "jnebs R0,5,label,0x1122334455"
+#define EXT_OPCODE 21 // Immediate value is one of *_EXT_OPCODE
+#define LDDW_OPCODE 22 // Load 4 bytes from data address (register + simm): "lddw R0, [5+R1]"
+#define STDW_OPCODE 23 // Store 4 bytes to data address (register + simm): "stdw R0, [5+R1]"
+#define WRITE_OPCODE 24 // Write 1, 2 or 4 bytes imm to the output buffer, e.g. "WRITE 5"
+// Copy the data from input packet or APF data region to output buffer. Register bit is
+// used to specify the source of data copy: R=0 means copy from packet, R=1 means copy
+// from APF data region. The source offset is encoded in the first imm and the copy length
+// is encoded in the second imm. "e.g. MEMCOPY(R=0), 5, 5"
+#define MEMCOPY_OPCODE 25
+
+// Extended opcodes. These all have an opcode of EXT_OPCODE
+// and specify the actual opcode in the immediate field.
+#define LDM_EXT_OPCODE 0 // Load from temporary memory, e.g. "ldm R0,5"
+ // Values 0-15 represent loading the different temporary memory slots.
+#define STM_EXT_OPCODE 16 // Store to temporary memory, e.g. "stm R0,5"
+ // Values 16-31 represent storing to the different temporary memory slots.
+#define NOT_EXT_OPCODE 32 // Not, e.g. "not R0"
+#define NEG_EXT_OPCODE 33 // Negate, e.g. "neg R0"
+#define SWAP_EXT_OPCODE 34 // Swap, e.g. "swap R0,R1"
+#define MOV_EXT_OPCODE 35 // Move, e.g. "move R0,R1"
+#define ALLOC_EXT_OPCODE 36 // Allocate buffer, "e.g. ALLOC R0"
+#define TRANS_EXT_OPCODE 37 // Transmit buffer, "e.g. TRANS R0"
+#define EWRITE1_EXT_OPCODE 38 // Write 1 byte from register to the output buffer, e.g. "EWRITE1 R0"
+#define EWRITE2_EXT_OPCODE 39 // Write 2 bytes from register to the output buffer, e.g. "EWRITE2 R0"
+#define EWRITE4_EXT_OPCODE 40 // Write 4 bytes from register to the output buffer, e.g. "EWRITE4 R0"
+// Copy the data from input packet to output buffer. The source offset is encoded as [Rx + second imm].
+// The copy length is encoded in the third imm. "e.g. EPKTCOPY [R0 + 5], 5"
+#define EPKTCOPY 41
+// Copy the data from APF data region to output buffer. The source offset is encoded as [Rx + second imm].
+// The copy length is encoded in the third imm. "e.g. EDATACOPY [R0 + 5], 5"
+#define EDATACOPY 42
+// It is executed as a jump, it tells how many bytes of the program regions
+// are used to store the data and followed by the actual data bytes.
+// "e.g. data 5, abcde"
+#define DATA_EXT_OPCODE 43
+
+#define EXTRACT_OPCODE(i) (((i) >> 3) & 31)
+#define EXTRACT_REGISTER(i) ((i) & 1)
+#define EXTRACT_IMM_LENGTH(i) (((i) >> 1) & 3)
+
+#endif // ANDROID_APF_APF_H
diff --git a/v5/apf_interpreter.c b/v5/apf_interpreter.c
new file mode 100644
index 0000000..dd1c620
--- /dev/null
+++ b/v5/apf_interpreter.c
@@ -0,0 +1,406 @@
+/*
+ * Copyright 2023, 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 "apf_interpreter.h"
+
+// TODO: Remove the dependency of the standard library and make the interpreter self-contained.
+#include <string.h>// For memcmp
+
+#include "apf.h"
+
+// User hook for interpreter debug tracing.
+#ifdef APF_TRACE_HOOK
+extern void APF_TRACE_HOOK(uint32_t pc, const uint32_t* regs, const uint8_t* program,
+ uint32_t program_len, const uint8_t *packet, uint32_t packet_len,
+ const uint32_t* memory, uint32_t ram_len);
+#else
+#define APF_TRACE_HOOK(pc, regs, program, program_len, packet, packet_len, memory, memory_len) \
+ do { /* nop*/ \
+ } while (0)
+#endif
+
+// Return code indicating "packet" should accepted.
+#define PASS_PACKET 1
+// Return code indicating "packet" should be dropped.
+#define DROP_PACKET 0
+// Verify an internal condition and accept packet if it fails.
+#define ASSERT_RETURN(c) if (!(c)) return PASS_PACKET
+// If "c" is of an unsigned type, generate a compile warning that gets promoted to an error.
+// This makes bounds checking simpler because ">= 0" can be avoided. Otherwise adding
+// superfluous ">= 0" with unsigned expressions generates compile warnings.
+#define ENFORCE_UNSIGNED(c) ((c)==(uint32_t)(c))
+
+uint32_t apf_version() {
+ return 20231122;
+}
+
+int apf_run(uint8_t* const program, const uint32_t program_len,
+ const uint32_t ram_len, const uint8_t* const packet,
+ const uint32_t packet_len, const uint32_t filter_age_16384ths) {
+// Is offset within program bounds?
+#define IN_PROGRAM_BOUNDS(p) (ENFORCE_UNSIGNED(p) && (p) < program_len)
+// Is offset within ram bounds?
+#define IN_RAM_BOUNDS(p) (ENFORCE_UNSIGNED(p) && (p) < ram_len)
+// Is offset within packet bounds?
+#define IN_PACKET_BOUNDS(p) (ENFORCE_UNSIGNED(p) && (p) < packet_len)
+// Is access to offset |p| length |size| within data bounds?
+#define IN_DATA_BOUNDS(p, size) (ENFORCE_UNSIGNED(p) && \
+ ENFORCE_UNSIGNED(size) && \
+ (p) + (size) <= ram_len && \
+ (p) >= program_len && \
+ (p) + (size) >= (p)) // catch wraparounds
+// Accept packet if not within program bounds
+#define ASSERT_IN_PROGRAM_BOUNDS(p) ASSERT_RETURN(IN_PROGRAM_BOUNDS(p))
+// Accept packet if not within ram bounds
+#define ASSERT_IN_RAM_BOUNDS(p) ASSERT_RETURN(IN_RAM_BOUNDS(p))
+// Accept packet if not within packet bounds
+#define ASSERT_IN_PACKET_BOUNDS(p) ASSERT_RETURN(IN_PACKET_BOUNDS(p))
+// Accept packet if not within data bounds
+#define ASSERT_IN_DATA_BOUNDS(p, size) ASSERT_RETURN(IN_DATA_BOUNDS(p, size))
+
+ // Program counter.
+ uint32_t pc = 0;
+// Accept packet if not within program or not ahead of program counter
+#define ASSERT_FORWARD_IN_PROGRAM(p) ASSERT_RETURN(IN_PROGRAM_BOUNDS(p) && (p) >= pc)
+ // Memory slot values.
+ uint32_t memory[MEMORY_ITEMS] = {};
+ // Fill in pre-filled memory slot values.
+ memory[MEMORY_OFFSET_OUTPUT_BUFFER_OFFSET] = 0;
+ memory[MEMORY_OFFSET_PROGRAM_SIZE] = program_len;
+ memory[MEMORY_OFFSET_DATA_SIZE] = ram_len;
+ memory[MEMORY_OFFSET_PACKET_SIZE] = packet_len;
+ memory[MEMORY_OFFSET_FILTER_AGE] = filter_age_16384ths >> 14;
+ ASSERT_IN_PACKET_BOUNDS(APF_FRAME_HEADER_SIZE);
+ // Only populate if IP version is IPv4.
+ if ((packet[APF_FRAME_HEADER_SIZE] & 0xf0) == 0x40) {
+ memory[MEMORY_OFFSET_IPV4_HEADER_SIZE] = (packet[APF_FRAME_HEADER_SIZE] & 15) * 4;
+ }
+ // Register values.
+ uint32_t registers[2] = {};
+ // Count of instructions remaining to execute. This is done to ensure an
+ // upper bound on execution time. It should never be hit and is only for
+ // safety. Initialize to the number of bytes in the program which is an
+ // upper bound on the number of instructions in the program.
+ uint32_t instructions_remaining = program_len;
+
+ // The output buffer pointer
+ uint8_t* allocated_buffer = NULL;
+ // The length of the output buffer
+ uint32_t allocate_buffer_len = 0;
+// Is access to offset |p| length |size| within output buffer bounds?
+#define IN_OUTPUT_BOUNDS(p, size) (ENFORCE_UNSIGNED(p) && \
+ ENFORCE_UNSIGNED(size) && \
+ (p) + (size) <= allocate_buffer_len && \
+ (p) >= 0 && \
+ (p) + (size) >= (p))
+// Accept packet if not write within allocated output buffer
+#define ASSERT_IN_OUTPUT_BOUNDS(p, size) ASSERT_RETURN(IN_OUTPUT_BOUNDS(p, size))
+
+// Decode the imm length.
+#define DECODE_IMM(value, length) \
+ for (uint32_t i = 0; i < (length) && pc < program_len; i++) \
+ value = (value << 8) | program[pc++]
+
+ do {
+ APF_TRACE_HOOK(pc, registers, program, program_len, packet, packet_len, memory, ram_len);
+ if (pc == program_len) {
+ return PASS_PACKET;
+ } else if (pc == (program_len + 1)) {
+ return DROP_PACKET;
+ }
+ ASSERT_IN_PROGRAM_BOUNDS(pc);
+ const uint8_t bytecode = program[pc++];
+ const uint32_t opcode = EXTRACT_OPCODE(bytecode);
+ const uint32_t reg_num = EXTRACT_REGISTER(bytecode);
+#define REG (registers[reg_num])
+#define OTHER_REG (registers[reg_num ^ 1])
+ // All instructions have immediate fields, so load them now.
+ const uint32_t len_field = EXTRACT_IMM_LENGTH(bytecode);
+ uint32_t imm = 0;
+ int32_t signed_imm = 0;
+ if (len_field != 0) {
+ const uint32_t imm_len = 1 << (len_field - 1);
+ ASSERT_FORWARD_IN_PROGRAM(pc + imm_len - 1);
+ DECODE_IMM(imm, imm_len);
+ // Sign extend imm into signed_imm.
+ signed_imm = imm << ((4 - imm_len) * 8);
+ signed_imm >>= (4 - imm_len) * 8;
+ }
+
+ switch (opcode) {
+ case LDB_OPCODE:
+ case LDH_OPCODE:
+ case LDW_OPCODE:
+ case LDBX_OPCODE:
+ case LDHX_OPCODE:
+ case LDWX_OPCODE: {
+ uint32_t offs = imm;
+ if (opcode >= LDBX_OPCODE) {
+ // Note: this can overflow and actually decrease offs.
+ offs += registers[1];
+ }
+ ASSERT_IN_PACKET_BOUNDS(offs);
+ uint32_t load_size;
+ switch (opcode) {
+ case LDB_OPCODE:
+ case LDBX_OPCODE:
+ load_size = 1;
+ break;
+ case LDH_OPCODE:
+ case LDHX_OPCODE:
+ load_size = 2;
+ break;
+ case LDW_OPCODE:
+ case LDWX_OPCODE:
+ load_size = 4;
+ break;
+ // Immediately enclosing switch statement guarantees
+ // opcode cannot be any other value.
+ }
+ const uint32_t end_offs = offs + (load_size - 1);
+ // Catch overflow/wrap-around.
+ ASSERT_RETURN(end_offs >= offs);
+ ASSERT_IN_PACKET_BOUNDS(end_offs);
+ uint32_t val = 0;
+ while (load_size--)
+ val = (val << 8) | packet[offs++];
+ REG = val;
+ break;
+ }
+ case JMP_OPCODE:
+ // This can jump backwards. Infinite looping prevented by instructions_remaining.
+ pc += imm;
+ break;
+ case JEQ_OPCODE:
+ case JNE_OPCODE:
+ case JGT_OPCODE:
+ case JLT_OPCODE:
+ case JSET_OPCODE:
+ case JNEBS_OPCODE: {
+ // Load second immediate field.
+ uint32_t cmp_imm = 0;
+ if (reg_num == 1) {
+ cmp_imm = registers[1];
+ } else if (len_field != 0) {
+ uint32_t cmp_imm_len = 1 << (len_field - 1);
+ ASSERT_FORWARD_IN_PROGRAM(pc + cmp_imm_len - 1);
+ DECODE_IMM(cmp_imm, cmp_imm_len);
+ }
+ switch (opcode) {
+ case JEQ_OPCODE:
+ if (registers[0] == cmp_imm)
+ pc += imm;
+ break;
+ case JNE_OPCODE:
+ if (registers[0] != cmp_imm)
+ pc += imm;
+ break;
+ case JGT_OPCODE:
+ if (registers[0] > cmp_imm)
+ pc += imm;
+ break;
+ case JLT_OPCODE:
+ if (registers[0] < cmp_imm)
+ pc += imm;
+ break;
+ case JSET_OPCODE:
+ if (registers[0] & cmp_imm)
+ pc += imm;
+ break;
+ case JNEBS_OPCODE: {
+ // cmp_imm is size in bytes of data to compare.
+ // pc is offset of program bytes to compare.
+ // imm is jump target offset.
+ // REG is offset of packet bytes to compare.
+ ASSERT_FORWARD_IN_PROGRAM(pc + cmp_imm - 1);
+ ASSERT_IN_PACKET_BOUNDS(REG);
+ const uint32_t last_packet_offs = REG + cmp_imm - 1;
+ ASSERT_RETURN(last_packet_offs >= REG);
+ ASSERT_IN_PACKET_BOUNDS(last_packet_offs);
+ if (memcmp(program + pc, packet + REG, cmp_imm))
+ pc += imm;
+ // skip past comparison bytes
+ pc += cmp_imm;
+ break;
+ }
+ }
+ break;
+ }
+ case ADD_OPCODE:
+ registers[0] += reg_num ? registers[1] : imm;
+ break;
+ case MUL_OPCODE:
+ registers[0] *= reg_num ? registers[1] : imm;
+ break;
+ case DIV_OPCODE: {
+ const uint32_t div_operand = reg_num ? registers[1] : imm;
+ ASSERT_RETURN(div_operand);
+ registers[0] /= div_operand;
+ break;
+ }
+ case AND_OPCODE:
+ registers[0] &= reg_num ? registers[1] : imm;
+ break;
+ case OR_OPCODE:
+ registers[0] |= reg_num ? registers[1] : imm;
+ break;
+ case SH_OPCODE: {
+ const int32_t shift_val = reg_num ? (int32_t)registers[1] : signed_imm;
+ if (shift_val > 0)
+ registers[0] <<= shift_val;
+ else
+ registers[0] >>= -shift_val;
+ break;
+ }
+ case LI_OPCODE:
+ REG = signed_imm;
+ break;
+ case EXT_OPCODE:
+ if (
+// If LDM_EXT_OPCODE is 0 and imm is compared with it, a compiler error will result,
+// instead just enforce that imm is unsigned (so it's always greater or equal to 0).
+#if LDM_EXT_OPCODE == 0
+ ENFORCE_UNSIGNED(imm) &&
+#else
+ imm >= LDM_EXT_OPCODE &&
+#endif
+ imm < (LDM_EXT_OPCODE + MEMORY_ITEMS)) {
+ REG = memory[imm - LDM_EXT_OPCODE];
+ } else if (imm >= STM_EXT_OPCODE && imm < (STM_EXT_OPCODE + MEMORY_ITEMS)) {
+ memory[imm - STM_EXT_OPCODE] = REG;
+ } else switch (imm) {
+ case NOT_EXT_OPCODE:
+ REG = ~REG;
+ break;
+ case NEG_EXT_OPCODE:
+ REG = -REG;
+ break;
+ case SWAP_EXT_OPCODE: {
+ uint32_t tmp = REG;
+ REG = OTHER_REG;
+ OTHER_REG = tmp;
+ break;
+ }
+ case MOV_EXT_OPCODE:
+ REG = OTHER_REG;
+ break;
+ case ALLOC_EXT_OPCODE:
+ ASSERT_RETURN(allocated_buffer == NULL);
+ allocate_buffer_len = REG;
+ allocated_buffer = apf_allocate_buffer(allocate_buffer_len);
+ ASSERT_RETURN(allocated_buffer != NULL);
+ memory[MEMORY_OFFSET_OUTPUT_BUFFER_OFFSET] = 0;
+ break;
+ case TRANS_EXT_OPCODE:
+ ASSERT_RETURN(allocated_buffer != NULL);
+ uint32_t pkt_len =
+ memory[MEMORY_OFFSET_OUTPUT_BUFFER_OFFSET];
+ // If pkt_len > allocate_buffer_len, it means sth. wrong
+ // happened and the allocated_buffer should be deallocated.
+ if (pkt_len > allocate_buffer_len) {
+ apf_transmit_buffer(
+ allocated_buffer,
+ 0 /* len */,
+ 0 /* dscp */);
+ return PASS_PACKET;
+ }
+ // TODO: calculate packet checksum and get dscp
+ apf_transmit_buffer(
+ allocated_buffer,
+ pkt_len,
+ 0 /* dscp */);
+ allocated_buffer = NULL;
+ break;
+ case DATA_EXT_OPCODE: {
+ ASSERT_FORWARD_IN_PROGRAM(pc + 1);
+ uint32_t skip_len = 0;
+ DECODE_IMM(skip_len, 2);
+ ASSERT_FORWARD_IN_PROGRAM(pc + skip_len - 1);
+ pc += skip_len;
+ break;
+ }
+ // Unknown extended opcode
+ default:
+ // Bail out
+ return PASS_PACKET;
+ }
+ break;
+ case LDDW_OPCODE: {
+ uint32_t offs = OTHER_REG + signed_imm;
+ uint32_t size = 4;
+ uint32_t val = 0;
+ // Negative offsets wrap around the end of the address space.
+ // This allows us to efficiently access the end of the
+ // address space with one-byte immediates without using %=.
+ if (offs & 0x80000000) {
+ offs = ram_len + offs; // unsigned overflow intended
+ }
+ ASSERT_IN_DATA_BOUNDS(offs, size);
+ while (size--)
+ val = (val << 8) | program[offs++];
+ REG = val;
+ break;
+ }
+ case STDW_OPCODE: {
+ uint32_t offs = OTHER_REG + signed_imm;
+ uint32_t size = 4;
+ uint32_t val = REG;
+ // Negative offsets wrap around the end of the address space.
+ // This allows us to efficiently access the end of the
+ // address space with one-byte immediates without using %=.
+ if (offs & 0x80000000) {
+ offs = ram_len + offs; // unsigned overflow intended
+ }
+ ASSERT_IN_DATA_BOUNDS(offs, size);
+ while (size--) {
+ program[offs++] = (val >> 24);
+ val <<= 8;
+ }
+ break;
+ }
+ case MEMCOPY_OPCODE: {
+ ASSERT_RETURN(allocated_buffer != NULL);
+ ASSERT_RETURN(len_field > 0);
+ uint32_t src_offs = imm;
+ uint32_t copy_len = 0;
+ DECODE_IMM(copy_len, 1);
+ uint32_t dst_offs = memory[MEMORY_OFFSET_OUTPUT_BUFFER_OFFSET];
+ ASSERT_IN_OUTPUT_BOUNDS(dst_offs, copy_len);
+ // reg_num == 0 copy from packet, reg_num == 1 copy from data.
+ if (reg_num == 0) {
+ ASSERT_IN_PACKET_BOUNDS(src_offs);
+ const uint32_t last_packet_offs = src_offs + copy_len - 1;
+ ASSERT_RETURN(last_packet_offs >= src_offs);
+ ASSERT_IN_PACKET_BOUNDS(last_packet_offs);
+ memmove(allocated_buffer + dst_offs, packet + src_offs,
+ copy_len);
+ } else {
+ ASSERT_IN_RAM_BOUNDS(src_offs + copy_len - 1);
+ memmove(allocated_buffer + dst_offs, program + src_offs,
+ copy_len);
+ }
+ dst_offs += copy_len;
+ memory[MEMORY_OFFSET_OUTPUT_BUFFER_OFFSET] = dst_offs;
+ break;
+ }
+ // Unknown opcode
+ default:
+ // Bail out
+ return PASS_PACKET;
+ }
+ } while (instructions_remaining--);
+ return PASS_PACKET;
+}
diff --git a/v5/apf_interpreter.h b/v5/apf_interpreter.h
new file mode 100644
index 0000000..914e356
--- /dev/null
+++ b/v5/apf_interpreter.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2023, 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.
+ */
+
+#ifndef APF_INTERPRETER_V5_H_
+#define APF_INTERPRETER_V5_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Returns the max version of the APF instruction set supported by apf_run().
+ * APFv6 is a superset of APFv4. APFv6 interpreters are able to run APFv4 code.
+ */
+uint32_t apf_version();
+
+/**
+ * Allocates a buffer for the APF program to build a reply packet.
+ *
+ * Unless in a critical low memory state, the firmware must allow allocating at
+ * least one 1500 byte buffer for every call to apf_run(). The interpreter will
+ * have at most one active allocation at any given time, and will always either
+ * transmit or deallocate the buffer before apf_run() returns.
+ *
+ * It is OK if the firmware decides to limit allocations to at most one per
+ * apf_run() invocation.
+ *
+ * The firmware MAY choose to allocate a larger buffer than requested, and
+ * give the apf_interpreter a pointer to the middle of the buffer. This will
+ * allow firmware to later (during or after apf_transmit_buffer call) populate
+ * any required headers, trailers, etc.
+ *
+ * @param size - the minimum size of buffer to allocate
+ * @return the pointer to the allocated region. The function can return NULL to
+ * indicate allocation failure, for example if too many buffers are
+ * pending transmit. Returning NULL will most likely result in the
+ * apf_run() returning PASS.
+ */
+uint8_t* apf_allocate_buffer(int size);
+
+/**
+ * Transmits the allocated buffer and deallocates it.
+ *
+ * The apf_interpreter will not read/write from/to the buffer once it calls
+ * this function.
+ *
+ * The content of the buffer between [ptr, ptr + len) are the bytes to be
+ * transmitted, starting from the ethernet header and not including any
+ * CRC bytes at the end.
+ *
+ * The firmware is expected to make its best effort to transmit. If it
+ * exhausts retries, or if there is no channel for too long and the transmit
+ * queue is full, then it is OK for the packet to be dropped. The firmware should
+ * prefer to fail allocation if transmit is likely to fail.
+ *
+ * apf_transmit_buffer() should be asynchronous, which means the actual packet
+ * transmission can happen sometime after the function returns.
+ *
+ * @param ptr - pointer to the transmit buffer, must have been previously
+ * returned by apf_allocate_buffer and not deallocated.
+ * @param len - the number of bytes to be transmitted (possibly less than
+ * the allocated buffer), 0 means don't transmit the buffer
+ * but only deallocate it
+ * @param dscp - the upper 6 bits of the TOS field in the IPv4 header or traffic
+ * class field in the IPv6 header.
+ * @return non-zero if the firmware *knows* the transmit will fail, zero if
+ * the firmware thinks the transmit will succeed. Returning an error
+ * will likely result in apf_run() returning PASS.
+ */
+int apf_transmit_buffer(uint8_t* ptr, int len, uint8_t dscp);
+
+/**
+ * Runs an APF program over a packet.
+ *
+ * The return value of apf_run indicates whether the packet should
+ * be passed or dropped. As a part of apf_run execution, the APF
+ * program can call apf_allocate_buffer()/apf_transmit_buffer() to construct
+ * a reply packet and transmit it.
+ *
+ * The text section containing the program instructions starts at address
+ * program and stops at + program_len - 1, and the writable data section
+ * begins at program + program_len and ends at program + ram_len - 1,
+ * as described in the following diagram:
+ *
+ *     program         program + program_len    program + ram_len
+ *        |    text section    | data section    |
+ *    +--------------------+------------------------+
+ *
+ * @param program - the program bytecode, followed by the writable data region.
+ * @param program_len - the length in bytes of the read-only portion of the APF
+ * buffer pointed to by {@code program}.
+ * @param ram_len - total length of the APF buffer pointed to by
+ * {@code program}, including the read-only bytecode
+ * portion and the read-write data portion.
+ * @param packet - the packet bytes, starting from the ethernet header.
+ * @param packet_len - the length of {@code packet} in bytes, not
+ * including trailers/CRC.
+ * @param filter_age_16384ths - the number of 1/16384 seconds since the filter
+ * was programmed.
+ *
+ * @return non-zero if packet should be passed, zero if packet should
+ * be dropped.
+ */
+int apf_run(uint8_t* const program, const uint32_t program_len,
+ const uint32_t ram_len, const uint8_t* const packet,
+ const uint32_t packet_len, const uint32_t filter_age_16384ths);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // APF_INTERPRETER_V5_H_
diff --git a/v5/test_buf_allocator.c b/v5/test_buf_allocator.c
new file mode 100644
index 0000000..867dba1
--- /dev/null
+++ b/v5/test_buf_allocator.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2023, 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 <string.h>
+
+#include "apf_interpreter.h"
+#include "test_buf_allocator.h"
+
+uint8_t apf_test_buffer[APF_TX_BUFFER_SIZE];
+uint8_t apf_test_tx_packet[APF_TX_BUFFER_SIZE];
+uint32_t apf_test_tx_packet_len;
+uint8_t apf_test_tx_dscp;
+
+/**
+ * Test implementation of apf_allocate_buffer()
+ *
+ * Clean up the apf_test_buffer and return the pointer to beginning of the buffer region.
+ */
+uint8_t* apf_allocate_buffer(int size) {
+ if (size > APF_TX_BUFFER_SIZE) {
+ return NULL;
+ }
+ memset(apf_test_buffer, 0, APF_TX_BUFFER_SIZE * sizeof(apf_test_buffer[0]));
+ return apf_test_buffer;
+}
+
+/**
+ * Test implementation of apf_transmit_buffer()
+ *
+ * Copy the content of allocated buffer to the apf_test_tx_packet region.
+ */
+int apf_transmit_buffer(uint8_t* ptr, int len, uint8_t dscp) {
+ apf_test_tx_packet_len = len;
+ apf_test_tx_dscp = dscp;
+ memcpy(apf_test_tx_packet, ptr, len);
+ return 0;
+}
diff --git a/v5/test_buf_allocator.h b/v5/test_buf_allocator.h
new file mode 100644
index 0000000..3916cf8
--- /dev/null
+++ b/v5/test_buf_allocator.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2023, 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 <stdint.h>
+
+#ifndef TEST_BUF_ALLOCATOR
+#define TEST_BUF_ALLOCATOR
+
+#define APF_TX_BUFFER_SIZE 1500
+
+extern uint8_t apf_test_buffer[APF_TX_BUFFER_SIZE];
+extern uint8_t apf_test_tx_packet[APF_TX_BUFFER_SIZE];
+extern uint32_t apf_test_tx_packet_len;
+extern uint8_t apf_test_tx_dscp;
+
+#endif // TEST_BUF_ALLOCATOR