diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-12-04 13:42:52 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-12-04 13:42:52 +0000 |
commit | c334d10891fe859e09f145e83f44843db2f30382 (patch) | |
tree | 7fc098108cb9df82a1e8ab972d5a6cfeb604ae98 | |
parent | cda8ba24815dd4cb258f51ffc79f3325ca2a39f2 (diff) | |
parent | d22e40555060c4064a78a8c4bf7ba392f138d46c (diff) | |
download | apf-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.bp | 14 | ||||
-rwxr-xr-x | apf_counter_decoder.py | 71 | ||||
-rw-r--r-- | apf_disassembler.c | 5 | ||||
-rw-r--r-- | apf_run.c | 131 | ||||
-rw-r--r-- | disassembler.c | 398 | ||||
-rw-r--r-- | disassembler.h | 24 | ||||
-rw-r--r-- | testdata/one_ra_with_counters.output | 8 | ||||
-rw-r--r-- | testdata/one_ra_with_counters_age_30.output | 8 | ||||
-rw-r--r-- | testdata/one_ra_with_counters_age_600.output | 8 | ||||
-rw-r--r-- | testdata/one_ra_with_counters_age_601.output | 8 | ||||
-rw-r--r-- | v5/Android.bp | 29 | ||||
-rw-r--r-- | v5/apf.h | 198 | ||||
-rw-r--r-- | v5/apf_interpreter.c | 406 | ||||
-rw-r--r-- | v5/apf_interpreter.h | 127 | ||||
-rw-r--r-- | v5/test_buf_allocator.c | 50 | ||||
-rw-r--r-- | v5/test_buf_allocator.h | 29 |
16 files changed, 1427 insertions, 87 deletions
@@ -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); } } @@ -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 |