diff --git a/.clang-format b/.clang-format index b5cbbd0ad..00d83fdc8 100644 --- a/.clang-format +++ b/.clang-format @@ -59,7 +59,7 @@ EmptyLineAfterAccessModifier: Never EmptyLineBeforeAccessModifier: Always ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true -ForEachMacros: ['bf_list_foreach', 'bf_list_foreach_rev', 'bf_rpack_array_foreach'] +ForEachMacros: ['bf_list_foreach', 'bf_list_foreach_rev', 'bf_rpack_array_foreach', 'bf_vector_foreach'] IncludeBlocks: Regroup IncludeCategories: # net/if.h needs to be included BEFORE linux/if.h to avoid conflicts diff --git a/src/libbpfilter/CMakeLists.txt b/src/libbpfilter/CMakeLists.txt index 47d98e674..de285df40 100644 --- a/src/libbpfilter/CMakeLists.txt +++ b/src/libbpfilter/CMakeLists.txt @@ -23,6 +23,7 @@ set(libbpfilter_srcs ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/if.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/io.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/core/list.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/core/vector.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/logger.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/matcher.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/pack.h @@ -46,6 +47,7 @@ set(libbpfilter_srcs ${CMAKE_CURRENT_SOURCE_DIR}/if.c ${CMAKE_CURRENT_SOURCE_DIR}/io.c ${CMAKE_CURRENT_SOURCE_DIR}/core/list.c + ${CMAKE_CURRENT_SOURCE_DIR}/core/vector.c ${CMAKE_CURRENT_SOURCE_DIR}/logger.c ${CMAKE_CURRENT_SOURCE_DIR}/matcher.c ${CMAKE_CURRENT_SOURCE_DIR}/pack.c diff --git a/src/libbpfilter/cgen/dump.c b/src/libbpfilter/cgen/dump.c index d5c860100..f3a728ca4 100644 --- a/src/libbpfilter/cgen/dump.c +++ b/src/libbpfilter/cgen/dump.c @@ -104,10 +104,14 @@ void bf_program_dump_bytecode(const struct bf_program *program) bf_dump_prefix_push(&prefix); - bf_dbg("Bytecode for program at %p, %lu insn:", program, program->img_size); + bf_dbg("Bytecode for program at %p, %lu insn:", program, program->img.size); - for (size_t i = 0; i < program->img_size; ++i) { - if (i == program->img_size - 1) + for (size_t i = 0; i < program->img.size; ++i) { + struct bpf_insn *insn = bf_vector_get(&program->img, i); + if (!insn) + bf_abort("bytecode dump: invalid instruction index %lu", i); + + if (i == program->img.size - 1) bf_dump_prefix_last(&prefix); if (double_insn) { @@ -116,10 +120,10 @@ void bf_program_dump_bytecode(const struct bf_program *program) continue; } - print_bpf_insn(&callbacks, &program->img[i], true); + print_bpf_insn(&callbacks, insn, true); ++bfdd.idx; - double_insn = program->img[i].code == (BPF_LD | BPF_IMM | BPF_DW); + double_insn = insn->code == (BPF_LD | BPF_IMM | BPF_DW); } // Force flush, otherwise output on stderr might appear. diff --git a/src/libbpfilter/cgen/jmp.c b/src/libbpfilter/cgen/jmp.c index 2bdca0b58..79d805b90 100644 --- a/src/libbpfilter/cgen/jmp.c +++ b/src/libbpfilter/cgen/jmp.c @@ -18,8 +18,14 @@ void bf_jmpctx_cleanup(struct bf_jmpctx *ctx) { if (ctx->program) { - struct bpf_insn *insn = &ctx->program->img[ctx->insn_idx]; - size_t off = ctx->program->img_size - ctx->insn_idx - 1U; + struct bpf_insn *insn = + bf_vector_get(&ctx->program->img, ctx->insn_idx); + if (!insn) { + bf_abort("jump fixup references invalid instruction index %lu", + ctx->insn_idx); + } + + size_t off = ctx->program->img.size - ctx->insn_idx - 1U; if (off > SHRT_MAX) bf_warn("jump offset overflow: %ld", off); diff --git a/src/libbpfilter/cgen/jmp.h b/src/libbpfilter/cgen/jmp.h index 87298980c..5ae64b3c4 100644 --- a/src/libbpfilter/cgen/jmp.h +++ b/src/libbpfilter/cgen/jmp.h @@ -57,7 +57,7 @@ struct bf_program; */ #define bf_jmpctx_get(program, insn) \ ({ \ - size_t __idx = (program)->img_size; \ + size_t __idx = (program)->img.size; \ int __r = bf_program_emit((program), (insn)); \ if (__r < 0) \ return __r; \ diff --git a/src/libbpfilter/cgen/program.c b/src/libbpfilter/cgen/program.c index 84f8d7b5f..f2b59d727 100644 --- a/src/libbpfilter/cgen/program.c +++ b/src/libbpfilter/cgen/program.c @@ -54,7 +54,6 @@ #define _BF_LOG_BUF_SIZE \ (UINT32_MAX >> 8) /* verifier maximum in kernels <= 5.1 */ -#define _BF_PROGRAM_DEFAULT_IMG_SIZE (1 << 6) #define _BF_LOG_MAP_N_ENTRIES 1000 #define _BF_LOG_MAP_SIZE \ _bf_round_next_power_of_2(sizeof(struct bf_log) * _BF_LOG_MAP_N_ENTRIES) @@ -107,6 +106,7 @@ int bf_program_new(struct bf_program **program, const struct bf_chain *chain, _program->flavor = bf_hook_to_flavor(chain->hook); _program->runtime.ops = bf_flavor_ops_get(_program->flavor); _program->runtime.chain = chain; + _program->img = bf_vector_default(sizeof(struct bpf_insn)); _program->fixups = bf_list_default(bf_fixup_free, NULL); _program->handle = handle; @@ -125,7 +125,7 @@ void bf_program_free(struct bf_program **program) return; bf_list_clean(&(*program)->fixups); - free((*program)->img); + bf_vector_clean(&(*program)->img); bf_printer_free(&(*program)->printer); @@ -152,9 +152,9 @@ void bf_program_dump(const struct bf_program *program, prefix_t *prefix) bf_printer_dump(program->printer, prefix); bf_dump_prefix_pop(prefix); - DUMP(prefix, "img: %p", program->img); - DUMP(prefix, "img_size: %lu", program->img_size); - DUMP(prefix, "img_cap: %lu", program->img_cap); + DUMP(prefix, "img: %p", program->img.data); + DUMP(prefix, "img.size: %lu", program->img.size); + DUMP(prefix, "img.cap: %lu", program->img.cap); DUMP(prefix, "fixups: bf_list[%lu]", bf_list_size(&program->fixups)); @@ -177,27 +177,6 @@ void bf_program_dump(const struct bf_program *program, prefix_t *prefix) bf_dump_prefix_pop(prefix); } -int bf_program_grow_img(struct bf_program *program) -{ - size_t new_cap = _BF_PROGRAM_DEFAULT_IMG_SIZE; - int r; - - assert(program); - - if (program->img) - new_cap = _bf_round_next_power_of_2(program->img_cap << 1); - - r = bf_realloc((void **)&program->img, new_cap * sizeof(struct bpf_insn)); - if (r < 0) { - return bf_err_r(r, "failed to grow program img from %lu to %lu insn", - program->img_cap, new_cap); - } - - program->img_cap = new_cap; - - return 0; -} - static void _bf_program_fixup_insn(struct bpf_insn *insn, enum bf_fixup_insn type, int32_t value) { @@ -230,16 +209,23 @@ static int _bf_program_fixup(struct bf_program *program, int32_t value; size_t offset; struct bf_fixup *fixup = bf_list_node_get_data(fixup_node); - struct bpf_insn *insn = &program->img[fixup->insn]; + struct bpf_insn *insn; struct bf_map *map; if (type != fixup->type) continue; + insn = bf_vector_get(&program->img, fixup->insn); + if (!insn) { + return bf_err_r(-EINVAL, + "fixup references invalid instruction index %lu", + fixup->insn); + } + switch (type) { case BF_FIXUP_TYPE_JMP_NEXT_RULE: insn_type = BF_FIXUP_INSN_OFF; - value = (int)(program->img_size - fixup->insn - 1U); + value = (int)(program->img.size - fixup->insn - 1U); break; case BF_FIXUP_TYPE_COUNTERS_MAP_FD: insn_type = BF_FIXUP_INSN_IMM; @@ -413,7 +399,7 @@ static int _bf_program_generate_elfstubs(struct bf_program *program) bf_list_foreach (&program->fixups, fixup_node) { struct bf_fixup *fixup = bf_list_node_get_data(fixup_node); - size_t off = program->img_size; + size_t off = program->img.size; if (fixup->type != BF_FIXUP_ELFSTUB_CALL) continue; @@ -430,7 +416,7 @@ static int _bf_program_generate_elfstubs(struct bf_program *program) fixup->attr.elfstub_id); } - start_at = program->img_size; + start_at = program->img.size; for (size_t i = 0; i < elfstub->ninsns; ++i) { r = bf_program_emit(program, elfstub->insns[i]); @@ -451,8 +437,18 @@ static int _bf_program_generate_elfstubs(struct bf_program *program) ld_insn[0].src_reg = BPF_PSEUDO_MAP_VALUE; ld_insn[1].imm = (int)bf_printer_msg_offset(msg); - program->img[insn_idx] = ld_insn[0]; - program->img[insn_idx + 1] = ld_insn[1]; + r = bf_vector_set(&program->img, insn_idx, &ld_insn[0]); + if (r) { + return bf_err_r( + r, "failed to set ELF stub instruction at index %lu", + insn_idx); + } + r = bf_vector_set(&program->img, insn_idx + 1, &ld_insn[1]); + if (r) { + return bf_err_r( + r, "failed to set ELF stub instruction at index %lu", + insn_idx + 1); + } r = bf_fixup_new(&fixup, BF_FIXUP_TYPE_PRINTER_MAP_FD, insn_idx, NULL); @@ -474,19 +470,9 @@ static int _bf_program_generate_elfstubs(struct bf_program *program) int bf_program_emit(struct bf_program *program, struct bpf_insn insn) { - int r; - assert(program); - if (program->img_size == program->img_cap) { - r = bf_program_grow_img(program); - if (r) - return r; - } - - program->img[program->img_size++] = insn; - - return 0; + return bf_vector_add(&program->img, &insn); } int bf_program_emit_kfunc_call(struct bf_program *program, const char *name) @@ -517,13 +503,11 @@ int bf_program_emit_fixup(struct bf_program *program, enum bf_fixup_type type, assert(program); - if (program->img_size == program->img_cap) { - r = bf_program_grow_img(program); - if (r) - return r; - } + r = bf_vector_reserve(&program->img, program->img.size + 1); + if (r) + return r; - r = bf_fixup_new(&fixup, type, program->img_size, attr); + r = bf_fixup_new(&fixup, type, program->img.size, attr); if (r) return r; @@ -535,8 +519,8 @@ int bf_program_emit_fixup(struct bf_program *program, enum bf_fixup_type type, /* This call could fail and return an error, in which case it is not * properly handled. However, this shouldn't be an issue as we previously - * test whether enough room is available in cgen.img, which is currently - * the only reason for EMIT() to fail. */ + * reserved enough room in program->img, which is currently the only + * reason for EMIT() to fail. */ EMIT(program, insn); return 0; @@ -550,13 +534,11 @@ int bf_program_emit_fixup_elfstub(struct bf_program *program, assert(program); - if (program->img_size == program->img_cap) { - r = bf_program_grow_img(program); - if (r) - return r; - } + r = bf_vector_reserve(&program->img, program->img.size + 1); + if (r) + return r; - r = bf_fixup_new(&fixup, BF_FIXUP_ELFSTUB_CALL, program->img_size, NULL); + r = bf_fixup_new(&fixup, BF_FIXUP_ELFSTUB_CALL, program->img.size, NULL); if (r) return r; @@ -808,13 +790,13 @@ int bf_program_load(struct bf_program *prog) r = bf_bpf_prog_load(prog->handle->prog_name, bf_hook_to_bpf_prog_type(prog->runtime.chain->hook), - prog->img, prog->img_size, + prog->img.data, prog->img.size, bf_hook_to_bpf_attach_type(prog->runtime.chain->hook), log_buf, log_buf ? _BF_LOG_BUF_SIZE : 0, bf_ctx_token(), &prog->handle->prog_fd); if (r) { - return bf_err_r(r, "failed to load bf_program (%lu bytes):\n%s\nerrno:", - prog->img_size, log_buf ? log_buf : ""); + return bf_err_r(r, "failed to load bf_program (%lu insns):\n%s\nerrno:", + prog->img.size, log_buf ? log_buf : ""); } return r; diff --git a/src/libbpfilter/cgen/program.h b/src/libbpfilter/cgen/program.h index 9be7c7efe..7b99a8fcc 100644 --- a/src/libbpfilter/cgen/program.h +++ b/src/libbpfilter/cgen/program.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -203,9 +204,7 @@ struct bf_program /* Bytecode */ uint32_t elfstubs_location[_BF_ELFSTUB_MAX]; - struct bpf_insn *img; - size_t img_size; - size_t img_cap; + bf_vector img; bf_list fixups; /** Runtime data used to interact with the program and cache information. @@ -238,7 +237,6 @@ int bf_program_new(struct bf_program **program, const struct bf_chain *chain, void bf_program_free(struct bf_program **program); void bf_program_dump(const struct bf_program *program, prefix_t *prefix); -int bf_program_grow_img(struct bf_program *program); int bf_program_emit(struct bf_program *program, struct bpf_insn insn); int bf_program_emit_kfunc_call(struct bf_program *program, const char *name); diff --git a/src/libbpfilter/core/vector.c b/src/libbpfilter/core/vector.c new file mode 100644 index 000000000..400f4c86b --- /dev/null +++ b/src/libbpfilter/core/vector.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + */ + +#include "bpfilter/core/vector.h" + +#include +#include +#include +#include + +#include "bpfilter/helper.h" + +#define _BF_VECTOR_INIT_CAP 8 +#define _BF_VECTOR_MAX_CAP (SIZE_MAX / 2) + +int bf_vector_new(bf_vector **vec, size_t elem_size) +{ + _free_bf_vector_ bf_vector *_vec = NULL; + + assert(vec); + + if (!elem_size) + return -EINVAL; + + _vec = calloc(1, sizeof(*_vec)); + if (!_vec) + return -ENOMEM; + + _vec->elem_size = elem_size; + + *vec = TAKE_PTR(_vec); + + return 0; +} + +void bf_vector_free(bf_vector **vec) +{ + assert(vec); + + if (!*vec) + return; + + bf_vector_clean(*vec); + freep((void *)vec); +} + +void bf_vector_clean(bf_vector *vec) +{ + assert(vec); + + freep((void *)&vec->data); + vec->size = 0; + vec->cap = 0; +} + +void *bf_vector_get(const bf_vector *vec, size_t index) +{ + assert(vec); + + if (index >= vec->size) + return NULL; + + return vec->data + (index * vec->elem_size); +} + +int bf_vector_set(bf_vector *vec, size_t index, const void *elem) +{ + assert(vec); + assert(elem); + + if (index >= vec->size) + return -EINVAL; + + memcpy(vec->data + (index * vec->elem_size), elem, vec->elem_size); + + return 0; +} + +int bf_vector_remove(bf_vector *vec, size_t index) +{ + assert(vec); + + if (index >= vec->size) + return -EINVAL; + + --vec->size; + + if (index < vec->size) { + memmove(vec->data + (index * vec->elem_size), + vec->data + ((index + 1) * vec->elem_size), + (vec->size - index) * vec->elem_size); + } + + return 0; +} + +static int _bf_vector_realloc(bf_vector *vec, size_t new_cap) +{ + size_t alloc_size; + int r; + + assert(vec); + + if (!vec->elem_size) + return -EINVAL; + + if (__builtin_mul_overflow(new_cap, vec->elem_size, &alloc_size)) + return -ENOMEM; + + r = bf_realloc(&vec->data, alloc_size); + if (r) + return r; + + vec->cap = new_cap; + + return 0; +} + +int bf_vector_add(bf_vector *vec, const void *elem) +{ + int r; + + assert(vec); + assert(elem); + + if (vec->size == vec->cap) { + size_t new_cap; + + new_cap = bf_min(vec->cap ? vec->cap * 2 : _BF_VECTOR_INIT_CAP, + _BF_VECTOR_MAX_CAP); + if (new_cap <= vec->cap) + return -ENOMEM; + + r = _bf_vector_realloc(vec, new_cap); + if (r) + return r; + } + + memcpy(vec->data + (vec->size * vec->elem_size), elem, vec->elem_size); + ++vec->size; + + return 0; +} + +int bf_vector_add_many(bf_vector *vec, const void *data, size_t count) +{ + size_t required; + int r; + + assert(vec); + assert(data); + + if (!count) + return 0; + + if (__builtin_add_overflow(vec->size, count, &required)) + return -ENOMEM; + + if (required > vec->cap) { + size_t new_cap; + + new_cap = bf_min( + bf_max(vec->cap ? vec->cap * 2 : _BF_VECTOR_INIT_CAP, required), + _BF_VECTOR_MAX_CAP); + if (new_cap < required) + return -ENOMEM; + + r = _bf_vector_realloc(vec, new_cap); + if (r) + return r; + } + + memcpy(vec->data + (vec->size * vec->elem_size), data, + count * vec->elem_size); + vec->size += count; + + return 0; +} + +int bf_vector_reserve(bf_vector *vec, size_t cap) +{ + assert(vec); + + if (cap <= vec->cap) + return 0; + + if (cap > _BF_VECTOR_MAX_CAP) + return -ENOMEM; + + return _bf_vector_realloc(vec, cap); +} + +void *bf_vector_take(bf_vector *vec) +{ + assert(vec); + + vec->size = 0; + vec->cap = 0; + + return TAKE_PTR(vec->data); +} diff --git a/src/libbpfilter/include/bpfilter/core/vector.h b/src/libbpfilter/include/bpfilter/core/vector.h new file mode 100644 index 000000000..2f6ee9f10 --- /dev/null +++ b/src/libbpfilter/include/bpfilter/core/vector.h @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + */ + +#pragma once + +#include + +/** + * @file vector.h + * + * Dynamically-sized array of fixed-size elements, backed by a single + * contiguous allocation. Elements are stored inline (not as pointers), + * so the caller decides the element type and size at initialization. + * It automatically grows, but never shrinks. + */ + +typedef struct bf_vector +{ + /// Backing buffer. NULL when the vector is empty and has + /// never been allocated. + void *data; + /// Number of elements currently stored. + size_t size; + /// Number of elements that can be stored before a reallocation is needed. + size_t cap; + /// Size of a single element in bytes. + size_t elem_size; +} bf_vector; + +#define _free_bf_vector_ __attribute__((cleanup(bf_vector_free))) +#define _clean_bf_vector_ __attribute__((cleanup(bf_vector_clean))) + +/** + * @brief Returns a zero-initialized `bf_vector` for elements of size `esz`. + * + * @param esz Size of a single element in bytes. Must be > 0. + * @return A zero-initialized `bf_vector`. + */ +#define bf_vector_default(esz) \ + ((bf_vector) {.data = NULL, .size = 0, .cap = 0, .elem_size = (esz)}) + +/** + * @brief Iterate over every element of a `bf_vector`. + * + * `elem` is declared as a pointer to the element type and will point to each + * element in turn. Do not add or remove elements during iteration. + * + * @param vec Pointer to the vector. Must be non-NULL. + * @param elem Name of the iteration variable. Will be declared as a + * `void *` and cast by the caller. + */ +#define bf_vector_foreach(vec, elem) \ + for (void *(elem) = (vec)->data; \ + (elem) && (elem) < (vec)->data + ((vec)->size * (vec)->elem_size); \ + (elem) = (elem) + (vec)->elem_size) + +/** + * @brief Allocate and initialise a new vector on the heap. + * + * @param vec Pointer to the vector pointer. Must be non-NULL. On failure, + * `*vec` is unchanged. + * @param elem_size Size of a single element in bytes. Must be > 0. + * @return 0 on success, or a negative errno value on failure. + */ +int bf_vector_new(bf_vector **vec, size_t elem_size); + +/** + * @brief Free a heap-allocated vector. + * + * @param vec Pointer to the vector pointer. Must be non-NULL. + */ +void bf_vector_free(bf_vector **vec); + +/** + * @brief Clean up a vector, freeing its backing buffer. + * + * After this call the vector can be reused (e.g. by re-assigning via + * `bf_vector_default`) or discarded. + * + * @param vec Pointer to the vector. Must be non-NULL. + */ +void bf_vector_clean(bf_vector *vec); + +/** + * @brief Get a pointer to the n-th element. + * + * @param vec Initialised vector. Must be non-NULL. + * @param index Index of the element. + * @return Pointer to the element, or NULL if @p index is out of bounds. + */ +void *bf_vector_get(const bf_vector *vec, size_t index); + +/** + * @brief Overwrite the n-th element. + * + * @param vec Initialised vector. Must be non-NULL. + * @param index Index of the element. + * @param elem Pointer to the new value. Must be non-NULL and point to at + * least `vec->elem_size` bytes. + * @return 0 on success, or -EINVAL if @p index is out of bounds. + */ +int bf_vector_set(bf_vector *vec, size_t index, const void *elem); + +/** + * @brief Remove the element at `index`, shifting subsequent elements left. + * + * @param vec Initialised vector. Must be non-NULL. + * @param index Index of the element to remove. + * @return 0 on success, or -EINVAL if `index` is out of bounds. + */ +int bf_vector_remove(bf_vector *vec, size_t index); + +/** + * @brief Append an element to the end of the vector, growing it if necessary. + * + * The element is copied from `elem` into the vector's backing buffer. + * + * @param vec Initialised vector. Must be non-NULL. + * @param elem Pointer to the element to copy in. Must be non-NULL and point + * to at least `vec->elem_size` bytes. + * @return 0 on success, or a negative errno value on failure. + */ +int bf_vector_add(bf_vector *vec, const void *elem); + +/** + * @brief Append multiple elements to the end of the vector. + * + * Copies `count` elements (each `vec->elem_size` bytes) from `data` into + * the vector, growing the backing buffer if necessary. + * + * @param vec Initialised vector. Must be non-NULL. + * @param data Pointer to the elements to copy in. Must be non-NULL and point + * to at least `count * vec->elem_size` bytes. Must not overlap with + * the vector's current contents. + * @param count Number of elements to append. If 0, this is a no-op. + * @return 0 on success, or a negative errno value on failure. + */ +int bf_vector_add_many(bf_vector *vec, const void *data, size_t count); + +/** + * @brief Ensure the vector has capacity for at least `cap` elements. + * + * If the current capacity is already >= `cap`, this is a no-op. + * Does not change the length or initialise any memory. + * + * @param vec Initialised vector. Must be non-NULL. + * @param cap Minimum number of elements the vector should be able to hold. + * @return 0 on success, or a negative errno value on failure. + */ +int bf_vector_reserve(bf_vector *vec, size_t cap); + +/** + * @brief Take ownership of the backing buffer. + * + * Returns the raw data pointer and resets the vector so it will + * re-allocate on the next add. + * + * @param vec Initialised vector. Must be non-NULL. + * @return Pointer to the backing buffer, or NULL if it was never allocated. + * The caller is responsible for freeing this memory. + */ +void *bf_vector_take(bf_vector *vec); diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index fbbb898d5..acf584900 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -83,10 +83,11 @@ bf_add_c_test(unit libbpfilter/hook.c) bf_add_c_test(unit libbpfilter/if.c) bf_add_c_test(unit libbpfilter/io.c) bf_add_c_test(unit libbpfilter/core/list.c) +bf_add_c_test(unit libbpfilter/core/vector.c) bf_add_c_test(unit libbpfilter/logger.c) bf_add_c_test(unit libbpfilter/matcher.c) bf_add_c_test(unit libbpfilter/pack.c) bf_add_c_test(unit libbpfilter/rule.c) bf_add_c_test(unit libbpfilter/set.c) bf_add_c_test(unit libbpfilter/verdict.c) -bf_add_c_test(unit libbpfilter/version.c) \ No newline at end of file +bf_add_c_test(unit libbpfilter/version.c) diff --git a/tests/unit/libbpfilter/core/vector.c b/tests/unit/libbpfilter/core/vector.c new file mode 100644 index 000000000..40cbfb523 --- /dev/null +++ b/tests/unit/libbpfilter/core/vector.c @@ -0,0 +1,312 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + */ + +#include + +#include +#include + +#include "test.h" + +static void new_and_free(void **state) +{ + (void)state; + + { + // Allocate and free + bf_vector *vec; + + assert_ok(bf_vector_new(&vec, sizeof(int))); + assert_int_equal(vec->size, 0); + assert_int_equal(vec->cap, 0); + assert_null(vec->data); + + bf_vector_free(&vec); + assert_null(vec); + } + + { + // Auto-free via cleanup attribute + _free_bf_vector_ bf_vector *vec = NULL; + assert_ok(bf_vector_new(&vec, sizeof(int))); + } + + { + // Auto-free on NULL + _free_bf_vector_ bf_vector *vec = NULL; + } +} + +static void init_and_clean(void **state) +{ + _clean_bf_vector_ bf_vector vec = bf_vector_default(sizeof(int)); + int val = 42; + + (void)state; + + assert_int_equal(vec.size, 0); + assert_int_equal(vec.cap, 0); + assert_null(vec.data); + + assert_ok(bf_vector_add(&vec, &val)); + assert_int_equal(vec.size, 1); + + bf_vector_clean(&vec); + assert_int_equal(vec.size, 0); + assert_int_equal(vec.cap, 0); + assert_null(vec.data); +} + +static void add_and_get(void **state) +{ + _clean_bf_vector_ bf_vector vec = bf_vector_default(sizeof(int)); + + (void)state; + + for (int i = 0; i < 100; ++i) + assert_ok(bf_vector_add(&vec, &i)); + + assert_int_equal(vec.size, 100); + assert_int_gte(vec.cap, 100); + + for (int i = 0; i < 100; ++i) { + int *p = bf_vector_get(&vec, i); + assert_non_null(p); + assert_int_equal(*p, i); + } +} + +static void remove_elem(void **state) +{ + _clean_bf_vector_ bf_vector vec = bf_vector_default(sizeof(int)); + + (void)state; + + for (int i = 0; i < 5; ++i) + assert_ok(bf_vector_add(&vec, &i)); + + // Remove from the middle: [0,1,2,3,4] -> [0,1,3,4] + assert_ok(bf_vector_remove(&vec, 2)); + assert_int_equal(vec.size, 4); + assert_int_equal(*(int *)bf_vector_get(&vec, 0), 0); + assert_int_equal(*(int *)bf_vector_get(&vec, 1), 1); + assert_int_equal(*(int *)bf_vector_get(&vec, 2), 3); + assert_int_equal(*(int *)bf_vector_get(&vec, 3), 4); + + // Remove last element: [0,1,3,4] -> [0,1,3] + assert_ok(bf_vector_remove(&vec, 3)); + assert_int_equal(vec.size, 3); + + // Remove first element: [0,1,3] -> [1,3] + assert_ok(bf_vector_remove(&vec, 0)); + assert_int_equal(vec.size, 2); + assert_int_equal(*(int *)bf_vector_get(&vec, 0), 1); + assert_int_equal(*(int *)bf_vector_get(&vec, 1), 3); + + // Out of bounds + assert_err(bf_vector_remove(&vec, 2)); + assert_err(bf_vector_remove(&vec, 99)); +} + +static void foreach(void **state) +{ + _clean_bf_vector_ bf_vector vec = bf_vector_default(sizeof(int)); + int expected = 0; + + (void)state; + + for (int i = 0; i < 50; ++i) + assert_ok(bf_vector_add(&vec, &i)); + + bf_vector_foreach (&vec, elem) { + assert_int_equal(*(int *)elem, expected); + ++expected; + } + + assert_int_equal(expected, 50); +} + +static void foreach_empty(void **state) +{ + _clean_bf_vector_ bf_vector vec = bf_vector_default(sizeof(int)); + int count = 0; + + (void)state; + + bf_vector_foreach (&vec, elem) { + (void)elem; + ++count; + } + + assert_int_equal(count, 0); +} + +static void reserve(void **state) +{ + _clean_bf_vector_ bf_vector vec = bf_vector_default(sizeof(int)); + + (void)state; + + // Initial capacity should be 0 and data NULL. + assert_int_equal(vec.cap, 0); + assert_null(vec.data); + + // Reserve on a fresh empty vector (exercises realloc(NULL, size) path). + assert_ok(bf_vector_reserve(&vec, 10)); + assert_int_gte(vec.cap, 10); + assert_int_equal(vec.size, 0); + assert_non_null(vec.data); + + // Add elements after the initial reserve. + for (int i = 0; i < 10; ++i) + assert_ok(bf_vector_add(&vec, &i)); + assert_int_equal(vec.size, 10); + + // Reserve more capacity on a non-empty vector. + assert_ok(bf_vector_reserve(&vec, 64)); + assert_int_gte(vec.cap, 64); + assert_int_equal(vec.size, 10); + + // Add elements after the second reserve. + for (int i = 10; i < 20; ++i) + assert_ok(bf_vector_add(&vec, &i)); + assert_int_gte(vec.cap, 64); + assert_int_equal(vec.size, 20); + + // All elements should be present. + for (int i = 0; i < 20; ++i) + assert_int_equal(*(int *)bf_vector_get(&vec, i), i); +} + +static void new_zero_elem_size(void **state) +{ + bf_vector *vec = NULL; + + (void)state; + + assert_err(bf_vector_new(&vec, 0)); + assert_null(vec); +} + +static void set(void **state) +{ + _clean_bf_vector_ bf_vector vec = bf_vector_default(sizeof(int)); + + (void)state; + + for (int i = 0; i < 5; ++i) + assert_ok(bf_vector_add(&vec, &i)); + + // Overwrite element at index 2: [0,1,2,3,4] -> [0,1,99,3,4] + int val = 99; + assert_ok(bf_vector_set(&vec, 2, &val)); + assert_int_equal(*(int *)bf_vector_get(&vec, 2), 99); + + // Other elements unchanged + assert_int_equal(*(int *)bf_vector_get(&vec, 0), 0); + assert_int_equal(*(int *)bf_vector_get(&vec, 1), 1); + assert_int_equal(*(int *)bf_vector_get(&vec, 3), 3); + assert_int_equal(*(int *)bf_vector_get(&vec, 4), 4); + assert_int_equal(vec.size, 5); + + // Out of bounds + assert_null(bf_vector_get(&vec, 5)); + assert_null(bf_vector_get(&vec, 99)); + assert_err(bf_vector_set(&vec, 5, &val)); + assert_err(bf_vector_set(&vec, 99, &val)); +} + +static void add_many(void **state) +{ + _clean_bf_vector_ bf_vector vec = bf_vector_default(sizeof(int)); + int vals[] = {10, 20, 30, 40, 50}; + + (void)state; + + // Bulk-append into empty vector + assert_ok(bf_vector_add_many(&vec, vals, ARRAY_SIZE(vals))); + assert_int_equal(vec.size, 5); + + for (size_t i = 0; i < ARRAY_SIZE(vals); ++i) + assert_int_equal(*(int *)bf_vector_get(&vec, i), vals[i]); + + // Bulk-append on top of existing elements + int more[] = {60, 70}; + assert_ok(bf_vector_add_many(&vec, more, ARRAY_SIZE(more))); + assert_int_equal(vec.size, 7); + assert_int_equal(*(int *)bf_vector_get(&vec, 5), 60); + assert_int_equal(*(int *)bf_vector_get(&vec, 6), 70); + + // Zero-count is a no-op + assert_ok(bf_vector_add_many(&vec, vals, 0)); + assert_int_equal(vec.size, 7); +} + +static void add_many_bytes(void **state) +{ + _clean_bf_vector_ bf_vector vec = bf_vector_default(1); + uint8_t chunk1[] = {0xaa, 0xbb, 0xcc}; + uint8_t chunk2[] = {0xdd, 0xee}; + + (void)state; + + assert_ok(bf_vector_add_many(&vec, chunk1, sizeof(chunk1))); + assert_ok(bf_vector_add_many(&vec, chunk2, sizeof(chunk2))); + assert_int_equal(vec.size, 5); + + uint8_t *p = vec.data; + assert_int_equal(p[0], 0xaa); + assert_int_equal(p[1], 0xbb); + assert_int_equal(p[2], 0xcc); + assert_int_equal(p[3], 0xdd); + assert_int_equal(p[4], 0xee); +} + +static void take(void **state) +{ + _clean_bf_vector_ bf_vector vec = bf_vector_default(sizeof(int)); + + (void)state; + + // Take from empty vector returns NULL + assert_null(bf_vector_take(&vec)); + + for (int i = 0; i < 5; ++i) + assert_ok(bf_vector_add(&vec, &i)); + + assert_int_equal(vec.size, 5); + + _cleanup_free_ void *buf = bf_vector_take(&vec); + assert_non_null(buf); + + // Vector is reset after take + assert_int_equal(vec.size, 0); + assert_int_equal(vec.cap, 0); + assert_null(vec.data); + + // Taken buffer still has the data + for (int i = 0; i < 5; ++i) + assert_int_equal(((int *)buf)[i], i); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(new_and_free), + cmocka_unit_test(init_and_clean), + cmocka_unit_test(add_and_get), + cmocka_unit_test(remove_elem), + cmocka_unit_test(foreach), + cmocka_unit_test(foreach_empty), + cmocka_unit_test(reserve), + cmocka_unit_test(new_zero_elem_size), + cmocka_unit_test(set), + cmocka_unit_test(add_many), + cmocka_unit_test(add_many_bytes), + cmocka_unit_test(take), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +}