From db743d1ee037e3986ed6b4cd6dbc568d45f47348 Mon Sep 17 00:00:00 2001 From: yaakov-stein Date: Tue, 10 Feb 2026 14:10:20 -0800 Subject: [PATCH 1/6] lib: helper: add FNV-1a hash function A simple data hashing function. --- src/libbpfilter/helper.c | 14 +++++++++++++ src/libbpfilter/include/bpfilter/helper.h | 20 +++++++++++++++++++ tests/unit/libbpfilter/helper.c | 24 +++++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/src/libbpfilter/helper.c b/src/libbpfilter/helper.c index 64ae9d230..cae1adf13 100644 --- a/src/libbpfilter/helper.c +++ b/src/libbpfilter/helper.c @@ -195,3 +195,17 @@ char *bf_trim(char *str) return bf_rtrim(bf_ltrim(str)); } + +uint64_t bf_fnv1a(const void *data, size_t len, uint64_t hash) +{ + assert(data); + + const uint8_t *bytes = data; + + for (size_t i = 0; i < len; ++i) { + hash ^= bytes[i]; + hash *= BF_FNV1A_PRIME; + } + + return hash; +} diff --git a/src/libbpfilter/include/bpfilter/helper.h b/src/libbpfilter/include/bpfilter/helper.h index fa2ef7de5..ca0c315b0 100644 --- a/src/libbpfilter/include/bpfilter/helper.h +++ b/src/libbpfilter/include/bpfilter/helper.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -394,3 +395,22 @@ int bf_read_file(const char *path, void **buf, size_t *len); * @return 0 on success, negative errno value on error. */ int bf_write_file(const char *path, const void *buf, size_t len); + +/// FNV-1a 64-bit offset basis. +/// @see https://en.wikipedia.org/wiki/Fowler-Noll-Vo_hash_function +#define BF_FNV1A_INIT 0xcbf29ce484222325ULL +/// FNV-1a 64-bit prime. +#define BF_FNV1A_PRIME 0x100000001b3ULL + +/** + * @brief Compute a FNV-1a 64-bit hash. + * + * Pass @ref BF_FNV1A_INIT as @p hash for the initial call. To hash + * multiple fields, chain calls by passing the previous return value. + * + * @param data Data to hash. Can't be NULL. + * @param len Number of bytes to hash. + * @param hash Initial or chained hash value. + * @return Updated hash value. + */ +uint64_t bf_fnv1a(const void *data, size_t len, uint64_t hash); diff --git a/tests/unit/libbpfilter/helper.c b/tests/unit/libbpfilter/helper.c index 8897e9269..6d5720d69 100644 --- a/tests/unit/libbpfilter/helper.c +++ b/tests/unit/libbpfilter/helper.c @@ -356,12 +356,36 @@ static void overwrite_existing_file(void **state) assert_memory_equal(read_buf, second_data, strlen(second_data)); } +static void fnv1a_hash(void **state) +{ + uint32_t val_a = 42; + uint32_t val_b = 99; + uint64_t hash_a; + uint64_t hash_b; + uint64_t hash_ab; + uint64_t hash_ba; + + (void)state; + + hash_a = bf_fnv1a(&val_a, sizeof(val_a), BF_FNV1A_INIT); + hash_b = bf_fnv1a(&val_b, sizeof(val_b), BF_FNV1A_INIT); + + assert_int_equal(hash_a, bf_fnv1a(&val_a, sizeof(val_a), BF_FNV1A_INIT)); + assert_int_not_equal(hash_a, hash_b); + + // Chaining: order matters for sequential hashing + hash_ab = bf_fnv1a(&val_b, sizeof(val_b), hash_a); + hash_ba = bf_fnv1a(&val_a, sizeof(val_a), hash_b); + assert_int_not_equal(hash_ab, hash_ba); +} + int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(close_fd), cmocka_unit_test(string_copy), cmocka_unit_test(realloc_mem), + cmocka_unit_test(fnv1a_hash), cmocka_unit_test(trim_left), cmocka_unit_test(trim_right), cmocka_unit_test(trim_both), From f91090bf6cbb9fee49cf9845254f57813d19e9ec Mon Sep 17 00:00:00 2001 From: Pawel Zmarzly Date: Thu, 12 Mar 2026 13:26:45 +0000 Subject: [PATCH 2/6] lib: data_structures: move bf_list into subfolder --- src/bfcli/chain.c | 2 +- src/bfcli/main.c | 2 +- src/bfcli/opts.c | 2 +- src/bfcli/parser.y | 4 ++-- src/bfcli/print.c | 2 +- src/bfcli/print.h | 2 +- src/bfcli/ruleset.h | 2 +- src/bpfilter/cgen/cgen.c | 2 +- src/bpfilter/cgen/cgen.h | 2 +- src/bpfilter/cgen/elfstub.h | 2 +- src/bpfilter/cgen/handle.c | 2 +- src/bpfilter/cgen/handle.h | 2 +- src/bpfilter/cgen/printer.c | 2 +- src/bpfilter/cgen/prog/link.h | 2 +- src/bpfilter/cgen/program.c | 2 +- src/bpfilter/cgen/program.h | 2 +- src/bpfilter/cgen/swich.c | 2 +- src/bpfilter/cgen/swich.h | 2 +- src/bpfilter/ctx.c | 2 +- src/bpfilter/ctx.h | 2 +- src/bpfilter/xlate.c | 2 +- src/libbpfilter/CMakeLists.txt | 4 ++-- src/libbpfilter/chain.c | 2 +- src/libbpfilter/cli.c | 2 +- src/libbpfilter/{ => data_structures}/list.c | 2 +- src/libbpfilter/hook.c | 2 +- src/libbpfilter/include/bpfilter/bpfilter.h | 2 +- src/libbpfilter/include/bpfilter/chain.h | 2 +- src/libbpfilter/include/bpfilter/{ => data_structures}/list.h | 0 src/libbpfilter/include/bpfilter/hook.h | 2 +- src/libbpfilter/include/bpfilter/rule.h | 2 +- src/libbpfilter/include/bpfilter/set.h | 2 +- src/libbpfilter/pack.c | 2 +- src/libbpfilter/rule.c | 2 +- src/libbpfilter/set.c | 2 +- tests/fuzz/fuzz_parser.c | 2 +- tests/harness/fake.c | 2 +- tests/harness/fake.h | 2 +- tests/harness/test.c | 2 +- tests/unit/CMakeLists.txt | 2 +- tests/unit/libbpfilter/chain.c | 2 +- tests/unit/libbpfilter/cli.c | 2 +- tests/unit/libbpfilter/{ => data_structures}/list.c | 2 +- tests/unit/libbpfilter/hook.c | 2 +- tests/unit/libbpfilter/rule.c | 2 +- 45 files changed, 46 insertions(+), 46 deletions(-) rename src/libbpfilter/{ => data_structures}/list.c (99%) rename src/libbpfilter/include/bpfilter/{ => data_structures}/list.h (100%) rename tests/unit/libbpfilter/{ => data_structures}/list.c (99%) diff --git a/src/bfcli/chain.c b/src/bfcli/chain.c index e0e1e2a2c..21589bc83 100644 --- a/src/bfcli/chain.c +++ b/src/bfcli/chain.c @@ -20,9 +20,9 @@ #include #include #include +#include #include #include -#include #include #include diff --git a/src/bfcli/main.c b/src/bfcli/main.c index 71c885000..c3b86567e 100644 --- a/src/bfcli/main.c +++ b/src/bfcli/main.c @@ -13,9 +13,9 @@ #include #include +#include #include #include -#include #include #include #include diff --git a/src/bfcli/opts.c b/src/bfcli/opts.c index 688d4507d..bc121eb80 100644 --- a/src/bfcli/opts.c +++ b/src/bfcli/opts.c @@ -9,7 +9,7 @@ #include -#include "bpfilter/list.h" +#include "bpfilter/data_structures/list.h" #include "chain.h" #include "ruleset.h" diff --git a/src/bfcli/parser.y b/src/bfcli/parser.y index ffa177095..55655eae1 100644 --- a/src/bfcli/parser.y +++ b/src/bfcli/parser.y @@ -9,7 +9,7 @@ #include #include - #include + #include #include #include "ruleset.h" @@ -30,7 +30,7 @@ #include #include #include - #include + #include #include #include #include diff --git a/src/bfcli/print.c b/src/bfcli/print.c index 9e07ed52b..b7c678f85 100644 --- a/src/bfcli/print.c +++ b/src/bfcli/print.c @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/bfcli/print.h b/src/bfcli/print.h index 210c8cea0..84c9138d0 100644 --- a/src/bfcli/print.h +++ b/src/bfcli/print.h @@ -8,7 +8,7 @@ #include -#include +#include #include struct bf_chain; diff --git a/src/bfcli/ruleset.h b/src/bfcli/ruleset.h index 22c1a9865..9838ea3d9 100644 --- a/src/bfcli/ruleset.h +++ b/src/bfcli/ruleset.h @@ -7,8 +7,8 @@ #pragma once #include +#include #include -#include #include #define bfc_ruleset_default() \ diff --git a/src/bpfilter/cgen/cgen.c b/src/bpfilter/cgen/cgen.c index 1d03b49da..f51b45971 100644 --- a/src/bpfilter/cgen/cgen.c +++ b/src/bpfilter/cgen/cgen.c @@ -17,11 +17,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include diff --git a/src/bpfilter/cgen/cgen.h b/src/bpfilter/cgen/cgen.h index dde1891ca..353539951 100644 --- a/src/bpfilter/cgen/cgen.h +++ b/src/bpfilter/cgen/cgen.h @@ -8,8 +8,8 @@ #include #include +#include #include -#include #include struct bf_chain; diff --git a/src/bpfilter/cgen/elfstub.h b/src/bpfilter/cgen/elfstub.h index aaf84c6d2..a46f22a58 100644 --- a/src/bpfilter/cgen/elfstub.h +++ b/src/bpfilter/cgen/elfstub.h @@ -7,7 +7,7 @@ #include -#include +#include /** * @file elfstub.h diff --git a/src/bpfilter/cgen/handle.c b/src/bpfilter/cgen/handle.c index 566878d10..4d41df435 100644 --- a/src/bpfilter/cgen/handle.c +++ b/src/bpfilter/cgen/handle.c @@ -13,10 +13,10 @@ #include #include +#include #include #include #include -#include #include #include diff --git a/src/bpfilter/cgen/handle.h b/src/bpfilter/cgen/handle.h index c3f1f5920..807641b90 100644 --- a/src/bpfilter/cgen/handle.h +++ b/src/bpfilter/cgen/handle.h @@ -7,9 +7,9 @@ #include +#include #include #include -#include #include struct bf_link; diff --git a/src/bpfilter/cgen/printer.c b/src/bpfilter/cgen/printer.c index e5456cbea..4070492e6 100644 --- a/src/bpfilter/cgen/printer.c +++ b/src/bpfilter/cgen/printer.c @@ -9,9 +9,9 @@ #include #include +#include #include #include -#include #include #include diff --git a/src/bpfilter/cgen/prog/link.h b/src/bpfilter/cgen/prog/link.h index 00d3d3f93..f34120de9 100644 --- a/src/bpfilter/cgen/prog/link.h +++ b/src/bpfilter/cgen/prog/link.h @@ -7,9 +7,9 @@ #include +#include #include #include -#include #include /** diff --git a/src/bpfilter/cgen/program.c b/src/bpfilter/cgen/program.c index 9f98f64e0..138b1245b 100644 --- a/src/bpfilter/cgen/program.c +++ b/src/bpfilter/cgen/program.c @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/bpfilter/cgen/program.h b/src/bpfilter/cgen/program.h index 6aa0434fe..718452a94 100644 --- a/src/bpfilter/cgen/program.h +++ b/src/bpfilter/cgen/program.h @@ -9,10 +9,10 @@ #include #include +#include #include #include #include -#include #include #include "cgen/elfstub.h" diff --git a/src/bpfilter/cgen/swich.c b/src/bpfilter/cgen/swich.c index 6721c8f9a..5659a779a 100644 --- a/src/bpfilter/cgen/swich.c +++ b/src/bpfilter/cgen/swich.c @@ -14,8 +14,8 @@ #include #include +#include #include -#include #include #include "cgen/jmp.h" diff --git a/src/bpfilter/cgen/swich.h b/src/bpfilter/cgen/swich.h index b907e5b1b..d3088fb58 100644 --- a/src/bpfilter/cgen/swich.h +++ b/src/bpfilter/cgen/swich.h @@ -10,8 +10,8 @@ #include #include +#include #include -#include /** * @file swich.h diff --git a/src/bpfilter/ctx.c b/src/bpfilter/ctx.c index c3405cfb8..3b6e0f53b 100644 --- a/src/bpfilter/ctx.c +++ b/src/bpfilter/ctx.c @@ -15,11 +15,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include diff --git a/src/bpfilter/ctx.h b/src/bpfilter/ctx.h index 20ece3ec4..442d5319a 100644 --- a/src/bpfilter/ctx.h +++ b/src/bpfilter/ctx.h @@ -8,8 +8,8 @@ #include #include +#include #include -#include #include "cgen/elfstub.h" diff --git a/src/bpfilter/xlate.c b/src/bpfilter/xlate.c index 1dcfd42f5..27079b8b7 100644 --- a/src/bpfilter/xlate.c +++ b/src/bpfilter/xlate.c @@ -7,10 +7,10 @@ #include #include +#include #include #include #include -#include #include #include #include diff --git a/src/libbpfilter/CMakeLists.txt b/src/libbpfilter/CMakeLists.txt index be6a5205c..154e768fc 100644 --- a/src/libbpfilter/CMakeLists.txt +++ b/src/libbpfilter/CMakeLists.txt @@ -19,7 +19,7 @@ set(libbpfilter_srcs ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/hook.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/if.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/io.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/list.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/data_structures/list.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/logger.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/matcher.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/ns.h @@ -44,7 +44,7 @@ set(libbpfilter_srcs ${CMAKE_CURRENT_SOURCE_DIR}/hook.c ${CMAKE_CURRENT_SOURCE_DIR}/if.c ${CMAKE_CURRENT_SOURCE_DIR}/io.c - ${CMAKE_CURRENT_SOURCE_DIR}/list.c + ${CMAKE_CURRENT_SOURCE_DIR}/data_structures/list.c ${CMAKE_CURRENT_SOURCE_DIR}/logger.c ${CMAKE_CURRENT_SOURCE_DIR}/matcher.c ${CMAKE_CURRENT_SOURCE_DIR}/pack.c diff --git a/src/libbpfilter/chain.c b/src/libbpfilter/chain.c index 9213febff..17000ffdf 100644 --- a/src/libbpfilter/chain.c +++ b/src/libbpfilter/chain.c @@ -9,10 +9,10 @@ #include #include +#include "bpfilter/data_structures/list.h" #include "bpfilter/dump.h" #include "bpfilter/helper.h" #include "bpfilter/hook.h" -#include "bpfilter/list.h" #include "bpfilter/logger.h" #include "bpfilter/pack.h" #include "bpfilter/rule.h" diff --git a/src/libbpfilter/cli.c b/src/libbpfilter/cli.c index be7dbabf2..48a2086ee 100644 --- a/src/libbpfilter/cli.c +++ b/src/libbpfilter/cli.c @@ -8,10 +8,10 @@ #include "bpfilter/chain.h" #include "bpfilter/counter.h" +#include "bpfilter/data_structures/list.h" #include "bpfilter/helper.h" #include "bpfilter/hook.h" #include "bpfilter/io.h" -#include "bpfilter/list.h" #include "bpfilter/logger.h" #include "bpfilter/pack.h" #include "bpfilter/request.h" diff --git a/src/libbpfilter/list.c b/src/libbpfilter/data_structures/list.c similarity index 99% rename from src/libbpfilter/list.c rename to src/libbpfilter/data_structures/list.c index c8d3b629c..612e2b730 100644 --- a/src/libbpfilter/list.c +++ b/src/libbpfilter/data_structures/list.c @@ -3,7 +3,7 @@ * Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */ -#include "bpfilter/list.h" +#include "bpfilter/data_structures/list.h" #include #include diff --git a/src/libbpfilter/hook.c b/src/libbpfilter/hook.c index 3f5b3b819..e94b0e174 100644 --- a/src/libbpfilter/hook.c +++ b/src/libbpfilter/hook.c @@ -17,10 +17,10 @@ #include #include +#include "bpfilter/data_structures/list.h" #include "bpfilter/dump.h" #include "bpfilter/flavor.h" #include "bpfilter/helper.h" -#include "bpfilter/list.h" #include "bpfilter/logger.h" #include "bpfilter/pack.h" diff --git a/src/libbpfilter/include/bpfilter/bpfilter.h b/src/libbpfilter/include/bpfilter/bpfilter.h index b27a38d0b..937c9b564 100644 --- a/src/libbpfilter/include/bpfilter/bpfilter.h +++ b/src/libbpfilter/include/bpfilter/bpfilter.h @@ -8,7 +8,7 @@ #include #include -#include +#include struct bf_response; struct bf_chain; diff --git a/src/libbpfilter/include/bpfilter/chain.h b/src/libbpfilter/include/bpfilter/chain.h index 82add0afa..2e4953df9 100644 --- a/src/libbpfilter/include/bpfilter/chain.h +++ b/src/libbpfilter/include/bpfilter/chain.h @@ -5,9 +5,9 @@ #pragma once +#include #include #include -#include #include #include diff --git a/src/libbpfilter/include/bpfilter/list.h b/src/libbpfilter/include/bpfilter/data_structures/list.h similarity index 100% rename from src/libbpfilter/include/bpfilter/list.h rename to src/libbpfilter/include/bpfilter/data_structures/list.h diff --git a/src/libbpfilter/include/bpfilter/hook.h b/src/libbpfilter/include/bpfilter/hook.h index 0412e75e8..bff241164 100644 --- a/src/libbpfilter/include/bpfilter/hook.h +++ b/src/libbpfilter/include/bpfilter/hook.h @@ -9,9 +9,9 @@ #include #include +#include #include #include -#include #include /** diff --git a/src/libbpfilter/include/bpfilter/rule.h b/src/libbpfilter/include/bpfilter/rule.h index 92adc7140..d29fbe93b 100644 --- a/src/libbpfilter/include/bpfilter/rule.h +++ b/src/libbpfilter/include/bpfilter/rule.h @@ -9,8 +9,8 @@ #include #include +#include #include -#include #include #include #include diff --git a/src/libbpfilter/include/bpfilter/set.h b/src/libbpfilter/include/bpfilter/set.h index dbc57daa9..4e7eb713f 100644 --- a/src/libbpfilter/include/bpfilter/set.h +++ b/src/libbpfilter/include/bpfilter/set.h @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include diff --git a/src/libbpfilter/pack.c b/src/libbpfilter/pack.c index d8fb02701..a9e1bcbc8 100644 --- a/src/libbpfilter/pack.c +++ b/src/libbpfilter/pack.c @@ -5,8 +5,8 @@ #include "bpfilter/pack.h" +#include "bpfilter/data_structures/list.h" #include "bpfilter/helper.h" -#include "bpfilter/list.h" #include "bpfilter/logger.h" #include "mpack.h" diff --git a/src/libbpfilter/rule.c b/src/libbpfilter/rule.c index e84d56404..30ae0cc19 100644 --- a/src/libbpfilter/rule.c +++ b/src/libbpfilter/rule.c @@ -10,9 +10,9 @@ #include #include +#include "bpfilter/data_structures/list.h" #include "bpfilter/dump.h" #include "bpfilter/helper.h" -#include "bpfilter/list.h" #include "bpfilter/logger.h" #include "bpfilter/matcher.h" #include "bpfilter/pack.h" diff --git a/src/libbpfilter/set.c b/src/libbpfilter/set.c index d576c6d68..ceeb31ba9 100644 --- a/src/libbpfilter/set.c +++ b/src/libbpfilter/set.c @@ -13,7 +13,7 @@ #include "bpfilter/dump.h" #include "bpfilter/helper.h" -#include "bpfilter/list.h" +#include "bpfilter/data_structures/list.h" #include "bpfilter/logger.h" #include "bpfilter/pack.h" diff --git a/tests/fuzz/fuzz_parser.c b/tests/fuzz/fuzz_parser.c index cfe517d32..d309e9167 100644 --- a/tests/fuzz/fuzz_parser.c +++ b/tests/fuzz/fuzz_parser.c @@ -9,7 +9,7 @@ #include #include -#include +#include #include "helper.h" #include "ruleset.h" diff --git a/tests/harness/fake.c b/tests/harness/fake.c index cf1eb6187..2c7ba0134 100644 --- a/tests/harness/fake.c +++ b/tests/harness/fake.c @@ -13,8 +13,8 @@ #include #include +#include #include -#include #include #include #include diff --git a/tests/harness/fake.h b/tests/harness/fake.h index da3e438bb..421b9dbc7 100644 --- a/tests/harness/fake.h +++ b/tests/harness/fake.h @@ -8,7 +8,7 @@ #include #include -#include +#include typedef bool (*bft_list_eq_cb)(const void *, const void *); typedef int (*bft_list_dummy_inserter)(bf_list *, void *); diff --git a/tests/harness/test.c b/tests/harness/test.c index af438fa66..856b81475 100644 --- a/tests/harness/test.c +++ b/tests/harness/test.c @@ -15,7 +15,7 @@ #include #include -#include "bpfilter/list.h" +#include "bpfilter/data_structures/list.h" int btf_setup_redirect_streams(void **state) { diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 3bb77535f..bf5523ec8 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -83,7 +83,7 @@ bf_add_c_test(unit libbpfilter/helper.c) 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/list.c) +bf_add_c_test(unit libbpfilter/data_structures/list.c) bf_add_c_test(unit libbpfilter/logger.c) bf_add_c_test(unit libbpfilter/matcher.c) bf_add_c_test(unit libbpfilter/ns.c) diff --git a/tests/unit/libbpfilter/chain.c b/tests/unit/libbpfilter/chain.c index 5a05000e4..e0fb7c499 100644 --- a/tests/unit/libbpfilter/chain.c +++ b/tests/unit/libbpfilter/chain.c @@ -4,7 +4,7 @@ */ #include -#include +#include #include #include #include diff --git a/tests/unit/libbpfilter/cli.c b/tests/unit/libbpfilter/cli.c index 3bcabea2b..c4d94b6f6 100644 --- a/tests/unit/libbpfilter/cli.c +++ b/tests/unit/libbpfilter/cli.c @@ -8,8 +8,8 @@ #include #include #include +#include #include -#include #include "fake.h" #include "test.h" diff --git a/tests/unit/libbpfilter/list.c b/tests/unit/libbpfilter/data_structures/list.c similarity index 99% rename from tests/unit/libbpfilter/list.c rename to tests/unit/libbpfilter/data_structures/list.c index bb6068f6f..aa8297270 100644 --- a/tests/unit/libbpfilter/list.c +++ b/tests/unit/libbpfilter/data_structures/list.c @@ -3,7 +3,7 @@ * Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */ -#include +#include #include "fake.h" #include "test.h" diff --git a/tests/unit/libbpfilter/hook.c b/tests/unit/libbpfilter/hook.c index d7ac09456..955665062 100644 --- a/tests/unit/libbpfilter/hook.c +++ b/tests/unit/libbpfilter/hook.c @@ -7,8 +7,8 @@ #include +#include "bpfilter/data_structures/list.h" #include "bpfilter/dump.h" -#include "bpfilter/list.h" #include "bpfilter/pack.h" #include "fake.h" #include "test.h" diff --git a/tests/unit/libbpfilter/rule.c b/tests/unit/libbpfilter/rule.c index 819cf3187..f04c11700 100644 --- a/tests/unit/libbpfilter/rule.c +++ b/tests/unit/libbpfilter/rule.c @@ -5,7 +5,7 @@ #include -#include "bpfilter/list.h" +#include "bpfilter/data_structures/list.h" #include "bpfilter/pack.h" #include "bpfilter/runtime.h" #include "fake.h" From 383a4cdefe0a876b1aeb452d3746eb61c5892cd9 Mon Sep 17 00:00:00 2001 From: Pawel Zmarzly Date: Fri, 13 Feb 2026 18:25:47 +0000 Subject: [PATCH 3/6] lib: data_structures: add bf_vector --- .clang-format | 2 +- src/libbpfilter/CMakeLists.txt | 2 + src/libbpfilter/data_structures/vector.c | 148 +++++++++++++ .../include/bpfilter/data_structures/vector.h | 167 +++++++++++++++ tests/unit/CMakeLists.txt | 3 +- .../unit/libbpfilter/data_structures/vector.c | 199 ++++++++++++++++++ 6 files changed, 519 insertions(+), 2 deletions(-) create mode 100644 src/libbpfilter/data_structures/vector.c create mode 100644 src/libbpfilter/include/bpfilter/data_structures/vector.h create mode 100644 tests/unit/libbpfilter/data_structures/vector.c 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 154e768fc..aedd7ea5e 100644 --- a/src/libbpfilter/CMakeLists.txt +++ b/src/libbpfilter/CMakeLists.txt @@ -29,6 +29,7 @@ set(libbpfilter_srcs ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/rule.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/runtime.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/set.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/data_structures/vector.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/verdict.h # Private sources and headers @@ -53,6 +54,7 @@ set(libbpfilter_srcs ${CMAKE_CURRENT_SOURCE_DIR}/response.c ${CMAKE_CURRENT_SOURCE_DIR}/rule.c ${CMAKE_CURRENT_SOURCE_DIR}/set.c + ${CMAKE_CURRENT_SOURCE_DIR}/data_structures/vector.c ${CMAKE_CURRENT_SOURCE_DIR}/verdict.c ${CMAKE_CURRENT_SOURCE_DIR}/version.c diff --git a/src/libbpfilter/data_structures/vector.c b/src/libbpfilter/data_structures/vector.c new file mode 100644 index 000000000..a699c18bd --- /dev/null +++ b/src/libbpfilter/data_structures/vector.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. + */ + +#include "bpfilter/data_structures/vector.h" + +#include +#include +#include +#include + +#include "bpfilter/helper.h" + +#define _BF_VECTOR_INIT_CAP 8 + +int bf_vector_new(struct bf_vector **vec, size_t elem_size) +{ + _free_bf_vector_ struct bf_vector *_vec = NULL; + + assert(vec); + assert(elem_size); + + _vec = calloc(1, sizeof(*_vec)); + if (!_vec) + return -ENOMEM; + + _vec->elem_size = elem_size; + + *vec = TAKE_PTR(_vec); + + return 0; +} + +void bf_vector_free(struct bf_vector **vec) +{ + assert(vec); + + if (!*vec) + return; + + bf_vector_clean(*vec); + free(*vec); + *vec = NULL; +} + +void bf_vector_clean(struct bf_vector *vec) +{ + assert(vec); + + freep((void *)&vec->data); + vec->len = 0; + vec->cap = 0; +} + +size_t bf_vector_len(const struct bf_vector *vec) +{ + assert(vec); + return vec->len; +} + +size_t bf_vector_cap(const struct bf_vector *vec) +{ + assert(vec); + return vec->cap; +} + +void *bf_vector_get(const struct bf_vector *vec, size_t index) +{ + assert(vec); + + if (index >= vec->len) + return NULL; + + return (char *)vec->data + (index * vec->elem_size); +} + +int bf_vector_add(struct bf_vector *vec, const void *elem) +{ + int r; + + assert(vec); + assert(elem); + + if (vec->len == vec->cap) { + if (vec->cap > SIZE_MAX / 2) + return -ENOMEM; + + size_t new_cap = vec->cap ? vec->cap * 2 : _BF_VECTOR_INIT_CAP; + + r = bf_vector_resize(vec, new_cap); + if (r) + return r; + } + + memcpy((char *)vec->data + (vec->len * vec->elem_size), elem, + vec->elem_size); + ++vec->len; + + return 0; +} + +int bf_vector_resize(struct bf_vector *vec, size_t new_cap) +{ + int r; + + assert(vec); + + if (new_cap < vec->len) + return -EINVAL; + + if (new_cap == 0) { + freep((void *)&vec->data); + vec->cap = 0; + return 0; + } + + size_t alloc_size; + + 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; +} + +void *bf_vector_data(const struct bf_vector *vec) +{ + assert(vec); + return vec->data; +} + +int bf_vector_set_len(struct bf_vector *vec, size_t len) +{ + assert(vec); + + if (len > vec->cap) + return -EINVAL; + + vec->len = len; + + return 0; +} diff --git a/src/libbpfilter/include/bpfilter/data_structures/vector.h b/src/libbpfilter/include/bpfilter/data_structures/vector.h new file mode 100644 index 000000000..7768a6c2c --- /dev/null +++ b/src/libbpfilter/include/bpfilter/data_structures/vector.h @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2026 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. + */ + +struct bf_vector; + +#define _free_bf_vector_ __attribute__((cleanup(bf_vector_free))) +#define _clean_bf_vector_ __attribute__((cleanup(bf_vector_clean))) + +/** + * @struct bf_vector + * + * @var bf_vector::data + * Backing buffer. NULL when the vector is empty and has never been allocated. + * @var bf_vector::len + * Number of elements currently stored. + * @var bf_vector::cap + * Number of elements that can be stored before a reallocation is needed. + * @var bf_vector::elem_size + * Size of a single element in bytes. + */ +struct bf_vector +{ + void *data; + size_t len; + size_t cap; + size_t elem_size; +}; + +/** + * @brief Returns a zero-initialized @ref bf_vector for elements of size @p esz. + * + * @param esz Size of a single element in bytes. + * @return A zero-initialized @ref bf_vector. + */ +#define bf_vector_default(esz) \ + (struct bf_vector) \ + { \ + .data = NULL, .len = 0, .cap = 0, .elem_size = (esz) \ + } + +/** + * @brief Iterate over every element of a @ref bf_vector. + * + * @p elem is declared as a pointer to the element type and will point to each + * element in turn. Safe to break out of but not to 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) < (void *)((char *)(vec)->data + \ + (vec)->len * (vec)->elem_size); \ + (elem) = (char *)(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(struct 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(struct 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 + * @ref bf_vector_default) or discarded. + * + * @param vec Pointer to the vector. Must be non-NULL. + */ +void bf_vector_clean(struct bf_vector *vec); + +/** + * @brief Get the number of elements in the vector. + * + * @param vec Initialised vector. Must be non-NULL. + * @return Number of elements stored. + */ +size_t bf_vector_len(const struct bf_vector *vec); + +/** + * @brief Get the current capacity. + * + * @param vec Initialised vector. Must be non-NULL. + * @return Number of elements that fit without reallocation. + */ +size_t bf_vector_cap(const struct 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. Must be < @ref bf_vector_len. + * @return Pointer to the element, or NULL if @p index is out of bounds. + */ +void *bf_vector_get(const struct 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 @p 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 @c vec->elem_size bytes. + * @return 0 on success, or a negative errno value on failure. + */ +int bf_vector_add(struct bf_vector *vec, const void *elem); + +/** + * @brief Resize the vector's backing buffer to hold exactly @p new_cap elements. + * + * @p new_cap must be >= the current length. If @p new_cap is 0 the backing + * buffer is freed. + * + * @param vec Initialised vector. Must be non-NULL. + * @param new_cap New capacity (in number of elements). + * @return 0 on success, or a negative errno value on failure. + */ +int bf_vector_resize(struct bf_vector *vec, size_t new_cap); + +/** + * @brief Get a pointer to the backing buffer. + * + * @param vec Initialised vector. Must be non-NULL. + * @return Pointer to the first byte, or NULL if the vector has never been + * allocated. + */ +void *bf_vector_data(const struct bf_vector *vec); + +/** + * @brief Set the number of live elements. + * + * @p len must be <= the current capacity. No initialization of the new + * elements is performed. + * + * @param vec Initialised vector. Must be non-NULL. + * @param len New element count. + * @return 0 on success, or -EINVAL if @p len exceeds the capacity. + */ +int bf_vector_set_len(struct bf_vector *vec, size_t len); diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index bf5523ec8..bcc4c5157 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -92,5 +92,6 @@ bf_add_c_test(unit libbpfilter/request.c) bf_add_c_test(unit libbpfilter/response.c) bf_add_c_test(unit libbpfilter/rule.c) bf_add_c_test(unit libbpfilter/set.c) +bf_add_c_test(unit libbpfilter/data_structures/vector.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/data_structures/vector.c b/tests/unit/libbpfilter/data_structures/vector.c new file mode 100644 index 000000000..bde837a9e --- /dev/null +++ b/tests/unit/libbpfilter/data_structures/vector.c @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. + */ + +#include +#include + +#include "test.h" + +static void new_and_free(void **state) +{ + (void)state; + + { + // Allocate and free, empty vector + struct bf_vector *vec; + + assert_ok(bf_vector_new(&vec, sizeof(int))); + assert_int_equal(bf_vector_len(vec), 0); + assert_int_equal(bf_vector_cap(vec), 0); + bf_vector_free(&vec); + assert_null(vec); + } + + { + // Auto-free via cleanup attribute + _free_bf_vector_ struct bf_vector *vec = NULL; + + assert_ok(bf_vector_new(&vec, sizeof(int))); + assert_int_equal(bf_vector_len(vec), 0); + } +} + +static void init_and_clean(void **state) +{ + _clean_bf_vector_ struct bf_vector vec = bf_vector_default(sizeof(int)); + + (void)state; + + assert_int_equal(bf_vector_len(&vec), 0); + assert_int_equal(bf_vector_cap(&vec), 0); + assert_null(vec.data); + + int val = 42; + assert_ok(bf_vector_add(&vec, &val)); + assert_int_equal(bf_vector_len(&vec), 1); + bf_vector_clean(&vec); + assert_int_equal(bf_vector_len(&vec), 0); + assert_int_equal(bf_vector_cap(&vec), 0); + assert_null(vec.data); +} + +static void default_macro(void **state) +{ + _clean_bf_vector_ struct bf_vector vec = bf_vector_default(sizeof(int)); + + (void)state; + + assert_int_equal(vec.len, 0); + assert_int_equal(vec.cap, 0); + assert_int_equal(vec.elem_size, sizeof(int)); + assert_null(vec.data); +} + +static void add_and_get(void **state) +{ + _clean_bf_vector_ struct 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(bf_vector_len(&vec), 100); + assert_int_gte(bf_vector_cap(&vec), 100); + + for (int i = 0; i < 100; ++i) { + int *p = bf_vector_get(&vec, i); + assert_non_null(p); + assert_int_equal(*p, i); + } + + // Out of bounds returns NULL + assert_null(bf_vector_get(&vec, 100)); + assert_null(bf_vector_get(&vec, 9999)); +} + +static void foreach(void **state) +{ + _clean_bf_vector_ struct 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_ struct 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 resize(void **state) +{ + _clean_bf_vector_ struct bf_vector vec = bf_vector_default(sizeof(int)); + + (void)state; + + // Resize up from empty + assert_ok(bf_vector_resize(&vec, 32)); + assert_int_equal(bf_vector_cap(&vec), 32); + assert_int_equal(bf_vector_len(&vec), 0); + + // Add some elements + for (int i = 0; i < 10; ++i) + assert_ok(bf_vector_add(&vec, &i)); + + assert_int_equal(bf_vector_len(&vec), 10); + + // Shrink to fit + assert_ok(bf_vector_resize(&vec, 10)); + assert_int_equal(bf_vector_cap(&vec), 10); + assert_int_equal(bf_vector_len(&vec), 10); + + // Data is preserved after resize + for (int i = 0; i < 10; ++i) + assert_int_equal(*(int *)bf_vector_get(&vec, i), i); + + // Can't shrink below current length + assert_err(bf_vector_resize(&vec, 5)); + assert_int_equal(bf_vector_cap(&vec), 10); + + // Resize to 0 when empty + bf_vector_clean(&vec); + vec = bf_vector_default(sizeof(int)); + assert_ok(bf_vector_resize(&vec, 0)); + assert_int_equal(bf_vector_cap(&vec), 0); + assert_null(vec.data); +} + +static void large_elements(void **state) +{ + struct big + { + char buf[256]; + }; + + _clean_bf_vector_ struct bf_vector vec = + bf_vector_default(sizeof(struct big)); + + (void)state; + + for (int i = 0; i < 20; ++i) { + struct big b; + memset(b.buf, i, sizeof(b.buf)); + assert_ok(bf_vector_add(&vec, &b)); + } + + assert_int_equal(bf_vector_len(&vec), 20); + + for (int i = 0; i < 20; ++i) { + struct big *p = bf_vector_get(&vec, i); + assert_non_null(p); + + for (size_t j = 0; j < sizeof(p->buf); ++j) + assert_int_equal((unsigned char)p->buf[j], (unsigned char)i); + } +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(new_and_free), cmocka_unit_test(init_and_clean), + cmocka_unit_test(default_macro), cmocka_unit_test(add_and_get), + cmocka_unit_test(foreach), cmocka_unit_test(foreach_empty), + cmocka_unit_test(resize), cmocka_unit_test(large_elements), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} From 3741a01cd493556c4a85257caf24bbff4daca3a5 Mon Sep 17 00:00:00 2001 From: Pawel Zmarzly Date: Tue, 17 Feb 2026 01:31:37 +0000 Subject: [PATCH 4/6] lib: set: add bf_set_foreach and bf_set_size --- .clang-format | 2 +- src/bfcli/print.c | 45 +++++++++++++------------- src/bpfilter/cgen/prog/map.c | 2 +- src/bpfilter/cgen/program.c | 8 ++--- src/libbpfilter/include/bpfilter/set.h | 31 ++++++++++++++++++ src/libbpfilter/set.c | 43 ++++++++++++++++++------ tests/harness/test.c | 2 +- tests/unit/libbpfilter/set.c | 16 ++++----- 8 files changed, 102 insertions(+), 47 deletions(-) diff --git a/.clang-format b/.clang-format index 00d83fdc8..af6437447 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', 'bf_vector_foreach'] +ForEachMacros: ['bf_list_foreach', 'bf_list_foreach_rev', 'bf_rpack_array_foreach', 'bf_set_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/bfcli/print.c b/src/bfcli/print.c index b7c678f85..564887d93 100644 --- a/src/bfcli/print.c +++ b/src/bfcli/print.c @@ -24,9 +24,9 @@ #include #include +#include #include #include -#include #include #include #include @@ -148,31 +148,31 @@ void bfc_chain_dump(struct bf_chain *chain, struct bf_hookopts *hookopts, bf_list_foreach (&chain->sets, set_node) { struct bf_set *set = bf_list_node_get_data(set_node); - if (!set->name) + if (!bf_set_get_name(set)) continue; - (void)fprintf(stdout, " set %s (", set->name); - for (size_t i = 0; i < set->n_comps; ++i) { - (void)fprintf(stdout, "%s", bf_matcher_type_to_str(set->key[i])); + (void)fprintf(stdout, " set %s (", bf_set_get_name(set)); + for (size_t i = 0; i < bf_set_get_n_comps(set); ++i) { + (void)fprintf(stdout, "%s", + bf_matcher_type_to_str(bf_set_get_key_comp(set, i))); - if (i != set->n_comps - 1) + if (i != bf_set_get_n_comps(set) - 1) (void)fprintf(stdout, ", "); } (void)fprintf(stdout, ") in {\n"); - bf_list_foreach (&set->elems, elem_node) { + bf_set_foreach (set, payload) { uint32_t payload_idx = 0; - void *payload = bf_list_node_get_data(elem_node); (void)fprintf(stdout, " "); - for (size_t i = 0; i < set->n_comps; ++i) { + for (size_t i = 0; i < bf_set_get_n_comps(set); ++i) { const struct bf_matcher_meta *meta = - bf_matcher_get_meta(set->key[i]); + bf_matcher_get_meta(bf_set_get_key_comp(set, i)); meta->ops[BF_MATCHER_IN].print(payload + payload_idx); payload_idx += meta->ops[BF_MATCHER_IN].ref_payload_size; - if (i != set->n_comps - 1) + if (i != bf_set_get_n_comps(set) - 1) (void)fprintf(stdout, ", "); } (void)fprintf(stdout, "\n"); @@ -196,34 +196,35 @@ void bfc_chain_dump(struct bf_chain *chain, struct bf_hookopts *hookopts, bf_chain_get_set_for_matcher(chain, matcher); (void)fprintf(stdout, " ("); - for (size_t i = 0; i < set->n_comps; ++i) { - (void)fprintf(stdout, "%s", - bf_matcher_type_to_str(set->key[i])); + for (size_t i = 0; i < bf_set_get_n_comps(set); ++i) { + (void)fprintf( + stdout, "%s", + bf_matcher_type_to_str(bf_set_get_key_comp(set, i))); - if (i != set->n_comps - 1) + if (i != bf_set_get_n_comps(set) - 1) (void)fprintf(stdout, ", "); } - if (set->name) { - (void)fprintf(stdout, ") in %s", set->name); + if (bf_set_get_name(set)) { + (void)fprintf(stdout, ") in %s", bf_set_get_name(set)); } else { (void)fprintf(stdout, ") in {\n"); - bf_list_foreach (&set->elems, elem_node) { + bf_set_foreach (set, payload) { uint32_t payload_idx = 0; - void *payload = bf_list_node_get_data(elem_node); (void)fprintf(stdout, " "); - for (size_t i = 0; i < set->n_comps; ++i) { + for (size_t i = 0; i < bf_set_get_n_comps(set); ++i) { const struct bf_matcher_meta *meta = - bf_matcher_get_meta(set->key[i]); + bf_matcher_get_meta( + bf_set_get_key_comp(set, i)); meta->ops[BF_MATCHER_IN].print(payload + payload_idx); payload_idx += meta->ops[BF_MATCHER_IN].ref_payload_size; - if (i != set->n_comps - 1) + if (i != bf_set_get_n_comps(set) - 1) (void)fprintf(stdout, ", "); } (void)fprintf(stdout, "\n"); diff --git a/src/bpfilter/cgen/prog/map.c b/src/bpfilter/cgen/prog/map.c index f52aa3d89..405e093cc 100644 --- a/src/bpfilter/cgen/prog/map.c +++ b/src/bpfilter/cgen/prog/map.c @@ -221,7 +221,7 @@ int bf_map_new_from_set(struct bf_map **map, const char *name, return _bf_map_new(map, name, BF_MAP_TYPE_SET, set->use_trie ? BF_BPF_MAP_TYPE_LPM_TRIE : BF_BPF_MAP_TYPE_HASH, - set->elem_size, 1, bf_list_size(&set->elems)); + set->elem_size, 1, bf_set_size(set)); } int bf_map_new_from_pack(struct bf_map **map, int dir_fd, bf_rpack_node_t node) diff --git a/src/bpfilter/cgen/program.c b/src/bpfilter/cgen/program.c index 138b1245b..c1c693854 100644 --- a/src/bpfilter/cgen/program.c +++ b/src/bpfilter/cgen/program.c @@ -23,12 +23,12 @@ #include #include #include +#include #include #include #include #include #include -#include #include #include #include @@ -697,7 +697,7 @@ static int _bf_program_load_sets_maps(struct bf_program *new_prog) _free_bf_map_ struct bf_map *map = NULL; _cleanup_free_ uint8_t *values = NULL; _cleanup_free_ uint8_t *keys = NULL; - size_t nelems = bf_list_size(&set->elems); + size_t nelems = bf_set_size(set); size_t idx = 0; if (!nelems) { @@ -721,9 +721,7 @@ static int _bf_program_load_sets_maps(struct bf_program *new_prog) if (!keys) return bf_err_r(errno, "failed to allocate map keys"); - bf_list_foreach (&set->elems, elem_node) { - void *elem = bf_list_node_get_data(elem_node); - + bf_set_foreach (set, elem) { memcpy(keys + (idx * set->elem_size), elem, set->elem_size); values[idx] = 1; ++idx; diff --git a/src/libbpfilter/include/bpfilter/set.h b/src/libbpfilter/include/bpfilter/set.h index 4e7eb713f..17f1f804a 100644 --- a/src/libbpfilter/include/bpfilter/set.h +++ b/src/libbpfilter/include/bpfilter/set.h @@ -65,6 +65,24 @@ struct bf_set bool use_trie; }; +/** + * @brief Iterate over the elements of a set. + * + * @param set Pointer to the set to iterate over. Must be non-NULL. + * @param elem_var Name of the variable containing the current element data + * (as `void *`). This variable will be created automatically. + */ +#define bf_set_foreach(set, elem_var) \ + for (bf_list_node *_bf_set_node = (set)->elems.head, \ + *_bf_set_next = _bf_set_node ? _bf_set_node->next : \ + NULL, \ + *_bf_set_brk = NULL; \ + _bf_set_node; _bf_set_node = _bf_set_brk ? NULL : _bf_set_next, \ + _bf_set_next = _bf_set_node ? _bf_set_node->next : NULL) \ + for (void *(elem_var) = (_bf_set_brk = (void *)1, \ + bf_list_node_get_data(_bf_set_node)); \ + _bf_set_brk; _bf_set_brk = NULL) + /** * @brief Allocate and initialise a new set. * @@ -126,6 +144,19 @@ void bf_set_dump(const struct bf_set *set, prefix_t *prefix); */ bool bf_set_is_empty(const struct bf_set *set); +/** + * @brief Get the number of elements in a set. + * + * @param set Initialised set. Can't be NULL. + * @return Number of elements in the set. + */ +size_t bf_set_size(const struct bf_set *set); + +const char *bf_set_get_name(const struct bf_set *set); +size_t bf_set_get_n_comps(const struct bf_set *set); +enum bf_matcher_type bf_set_get_key_comp(const struct bf_set *set, + size_t index); + int bf_set_add_elem(struct bf_set *set, const void *elem); /** diff --git a/src/libbpfilter/set.c b/src/libbpfilter/set.c index ceeb31ba9..7d4480514 100644 --- a/src/libbpfilter/set.c +++ b/src/libbpfilter/set.c @@ -346,8 +346,8 @@ int bf_set_pack(const struct bf_set *set, bf_wpack_t *pack) bf_wpack_close_array(pack); bf_wpack_open_array(pack, "elements"); - bf_list_foreach (&set->elems, elem_node) - bf_wpack_bin(pack, bf_list_node_get_data(elem_node), set->elem_size); + bf_set_foreach (set, elem) + bf_wpack_bin(pack, elem, set->elem_size); bf_wpack_close_array(pack); return bf_wpack_is_valid(pack) ? 0 : -EINVAL; @@ -374,7 +374,7 @@ void bf_set_dump(const struct bf_set *set, prefix_t *prefix) DUMP(prefix, "elem_size: %lu", set->elem_size); DUMP(bf_dump_prefix_last(prefix), "elems: bf_list[%lu]", - bf_list_size(&set->elems)); + bf_set_size(set)); bf_dump_prefix_push(prefix); bf_list_foreach (&set->elems, elem_node) { @@ -421,6 +421,35 @@ bool bf_set_is_empty(const struct bf_set *set) return bf_list_is_empty(&set->elems); } +size_t bf_set_size(const struct bf_set *set) +{ + assert(set); + + return bf_list_size(&set->elems); +} + +const char *bf_set_get_name(const struct bf_set *set) +{ + assert(set); + + return set->name; +} + +size_t bf_set_get_n_comps(const struct bf_set *set) +{ + assert(set); + + return set->n_comps; +} + +enum bf_matcher_type bf_set_get_key_comp(const struct bf_set *set, size_t index) +{ + assert(set); + assert(index < set->n_comps); + + return set->key[index]; +} + /** * @brief Check if two sets have the same key format. * @@ -466,9 +495,7 @@ int bf_set_add_many(struct bf_set *dest, struct bf_set **to_add) void *elem_to_add = bf_list_node_get_data(elem_node); bool found = false; - bf_list_foreach (&dest->elems, dest_elem_node) { - const void *dest_elem = bf_list_node_get_data(dest_elem_node); - + bf_set_foreach (dest, dest_elem) { if (memcmp(dest_elem, elem_to_add, dest->elem_size) == 0) { found = true; break; @@ -503,9 +530,7 @@ int bf_set_remove_many(struct bf_set *dest, struct bf_set **to_remove) return r; // @todo This has O(n * m) complexity. Could be O(m) if we used hashsets. - bf_list_foreach (&(*to_remove)->elems, elem_node) { - const void *elem_to_remove = bf_list_node_get_data(elem_node); - + bf_set_foreach (*to_remove, elem_to_remove) { bf_list_foreach (&dest->elems, dest_elem_node) { const void *dest_elem = bf_list_node_get_data(dest_elem_node); diff --git a/tests/harness/test.c b/tests/harness/test.c index 856b81475..2c9036606 100644 --- a/tests/harness/test.c +++ b/tests/harness/test.c @@ -232,7 +232,7 @@ bool bft_set_eq(const struct bf_set *lhs, const struct bf_set *rhs) { const struct bf_list_node *n0, *n1; - if (bf_list_size(&lhs->elems) != bf_list_size(&rhs->elems)) + if (bf_set_size(lhs) != bf_set_size(rhs)) return false; if (lhs->elem_size != rhs->elem_size) diff --git a/tests/unit/libbpfilter/set.c b/tests/unit/libbpfilter/set.c index d582b01ce..3fadfa132 100644 --- a/tests/unit/libbpfilter/set.c +++ b/tests/unit/libbpfilter/set.c @@ -102,7 +102,7 @@ static void add_elem(void **state) assert_ok(bf_set_new(&set, "test", key, ARRAY_SIZE(key))); assert_ok(bf_set_add_elem(set, &elem)); - assert_int_equal(bf_list_size(&set->elems), 1); + assert_int_equal(bf_set_size(set), 1); } static void add_multiple_elems(void **state) @@ -122,7 +122,7 @@ static void add_multiple_elems(void **state) assert_ok(bf_set_add_elem(set, elem)); } - assert_int_equal(bf_list_size(&set->elems), 5); + assert_int_equal(bf_set_size(set), 5); } static void pack_and_unpack(void **state) @@ -181,7 +181,7 @@ static void pack_and_unpack_empty(void **state) assert_ok(bf_set_new_from_pack(&destination, node)); assert_true(bft_set_eq(source, destination)); - assert_int_equal(bf_list_size(&destination->elems), 0); + assert_int_equal(bf_set_size(destination), 0); } static void dump(void **state) @@ -224,7 +224,7 @@ static void new_from_raw(void **state) assert_string_equal(set->name, "test_raw"); assert_int_equal(set->n_comps, 1); assert_int_equal(set->key[0], BF_MATCHER_IP4_SADDR); - assert_int_equal(bf_list_size(&set->elems), 2); + assert_int_equal(bf_set_size(set), 2); } static void new_from_raw_multiple_keys(void **state) @@ -240,7 +240,7 @@ static void new_from_raw_multiple_keys(void **state) assert_int_equal(set->n_comps, 2); assert_int_equal(set->key[0], BF_MATCHER_IP4_DADDR); assert_int_equal(set->key[1], BF_MATCHER_TCP_SPORT); - assert_int_equal(bf_list_size(&set->elems), 2); + assert_int_equal(bf_set_size(set), 2); } static void new_from_raw_invalid(void **state) @@ -280,7 +280,7 @@ static void add_many_basic(void **state) assert_ok(bf_set_add_many(dest, &to_add)); - assert_int_equal(bf_list_size(&dest->elems), 3); + assert_int_equal(bf_set_size(dest), 3); assert_int_equal(*(uint32_t *)bf_list_get_at(&dest->elems, 0), elem1); assert_int_equal(*(uint32_t *)bf_list_get_at(&dest->elems, 1), elem2); assert_int_equal(*(uint32_t *)bf_list_get_at(&dest->elems, 2), elem3); @@ -347,7 +347,7 @@ static void remove_many_basic(void **state) assert_ok(bf_set_remove_many(dest, &to_remove)); - assert_int_equal(bf_list_size(&dest->elems), 2); + assert_int_equal(bf_set_size(dest), 2); assert_int_equal(*(uint32_t *)bf_list_get_at(&dest->elems, 0), elem1); assert_int_equal(*(uint32_t *)bf_list_get_at(&dest->elems, 1), elem3); assert_null(to_remove); @@ -377,7 +377,7 @@ static void remove_many_disjoint_sets(void **state) assert_ok(bf_set_add_elem(to_remove, &elem4)); assert_ok(bf_set_remove_many(dest, &to_remove)); - assert_int_equal(bf_list_size(&dest->elems), 2); + assert_int_equal(bf_set_size(dest), 2); assert_int_equal(*(uint32_t *)bf_list_get_at(&dest->elems, 0), elem1); assert_int_equal(*(uint32_t *)bf_list_get_at(&dest->elems, 1), elem2); assert_null(to_remove); From 8601a5cecb5c1424ecb74279d27fa39002188945 Mon Sep 17 00:00:00 2001 From: Pawel Zmarzly Date: Thu, 12 Mar 2026 13:58:41 +0000 Subject: [PATCH 5/6] lib: data_structures: add bf_hashset --- .clang-format | 2 +- src/libbpfilter/CMakeLists.txt | 2 + src/libbpfilter/data_structures/hashset.c | 275 +++++++++++++ .../bpfilter/data_structures/hashset.h | 213 ++++++++++ tests/unit/CMakeLists.txt | 1 + .../libbpfilter/data_structures/hashset.c | 370 ++++++++++++++++++ 6 files changed, 862 insertions(+), 1 deletion(-) create mode 100644 src/libbpfilter/data_structures/hashset.c create mode 100644 src/libbpfilter/include/bpfilter/data_structures/hashset.h create mode 100644 tests/unit/libbpfilter/data_structures/hashset.c diff --git a/.clang-format b/.clang-format index af6437447..e5c1b1b8e 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', 'bf_set_foreach', 'bf_vector_foreach'] +ForEachMacros: ['bf_hashset_foreach', 'bf_list_foreach', 'bf_list_foreach_rev', 'bf_rpack_array_foreach', 'bf_set_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 aedd7ea5e..8c16d4a2f 100644 --- a/src/libbpfilter/CMakeLists.txt +++ b/src/libbpfilter/CMakeLists.txt @@ -19,6 +19,7 @@ set(libbpfilter_srcs ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/hook.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/if.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/io.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/data_structures/hashset.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/data_structures/list.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/logger.h ${CMAKE_CURRENT_SOURCE_DIR}/include/bpfilter/matcher.h @@ -45,6 +46,7 @@ set(libbpfilter_srcs ${CMAKE_CURRENT_SOURCE_DIR}/hook.c ${CMAKE_CURRENT_SOURCE_DIR}/if.c ${CMAKE_CURRENT_SOURCE_DIR}/io.c + ${CMAKE_CURRENT_SOURCE_DIR}/data_structures/hashset.c ${CMAKE_CURRENT_SOURCE_DIR}/data_structures/list.c ${CMAKE_CURRENT_SOURCE_DIR}/logger.c ${CMAKE_CURRENT_SOURCE_DIR}/matcher.c diff --git a/src/libbpfilter/data_structures/hashset.c b/src/libbpfilter/data_structures/hashset.c new file mode 100644 index 000000000..9e6a8f37f --- /dev/null +++ b/src/libbpfilter/data_structures/hashset.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. + */ + +#include "bpfilter/data_structures/hashset.h" + +#include +#include +#include + +#include "bpfilter/data_structures/vector.h" +#include "bpfilter/helper.h" + +#define _BF_HASHSET_TOMBSTONE ((void *)1) +#define _BF_HASHSET_INIT_CAP 16 +#define _BF_HASHSET_MAX_LOAD_NUM 7 +#define _BF_HASHSET_MAX_LOAD_DEN 10 + +static inline size_t _bf_n_slots(const bf_hashset *set) +{ + return bf_vector_len(&set->slots); +} + +static inline void **_bf_slot_at(const bf_hashset *set, size_t index) +{ + return (void **)bf_vector_get(&set->slots, index); +} + +static inline bool _bf_slot_is_live(void *slot) +{ + return slot && slot != _BF_HASHSET_TOMBSTONE; +} + +static size_t _bf_hashset_index(const bf_hashset *set, const void *data) +{ + return set->ops.hash(data, set->ctx) % _bf_n_slots(set); +} + +static void _bf_hashset_insert_unchecked(bf_hashset *set, void *data) +{ + size_t n = _bf_n_slots(set); + size_t idx = _bf_hashset_index(set, data); + + while (_bf_slot_is_live(*_bf_slot_at(set, idx))) + idx = (idx + 1) % n; + + *_bf_slot_at(set, idx) = data; + ++set->len; + ++set->n_used; +} + +static int _bf_hashset_grow(bf_hashset *set) +{ + size_t old_n_slots = _bf_n_slots(set); + struct bf_vector old_slots; + size_t new_n_slots; + int r; + + if (old_n_slots > SIZE_MAX / 2) + return -ENOMEM; + + new_n_slots = old_n_slots ? old_n_slots * 2 : _BF_HASHSET_INIT_CAP; + + old_slots = TAKE_STRUCT(set->slots); + + set->slots = bf_vector_default(sizeof(void *)); + + r = bf_vector_resize(&set->slots, new_n_slots); + if (r) { + bf_vector_clean(&set->slots); + set->slots = old_slots; + return r; + } + + memset(bf_vector_data(&set->slots), 0, new_n_slots * sizeof(void *)); + (void)bf_vector_set_len(&set->slots, new_n_slots); + + set->len = 0; + set->n_used = 0; + + for (size_t i = 0; i < old_n_slots; ++i) { + void *ptr = *(void **)bf_vector_get(&old_slots, i); + + if (!_bf_slot_is_live(ptr)) + continue; + + _bf_hashset_insert_unchecked(set, ptr); + } + + bf_vector_clean(&old_slots); + + return 0; +} + +static bool _bf_hashset_needs_grow(const bf_hashset *set) +{ + size_t n = _bf_n_slots(set); + + if (n == 0) + return true; + + return set->n_used * _BF_HASHSET_MAX_LOAD_DEN >= + n * _BF_HASHSET_MAX_LOAD_NUM; +} + +static bool _bf_hashset_find(const bf_hashset *set, const void *data, + size_t *index) +{ + size_t n; + size_t idx; + + assert(set); + assert(data); + + n = _bf_n_slots(set); + if (n == 0) + return false; + + idx = _bf_hashset_index(set, data); + + for (size_t i = 0; i < n; ++i) { + void *slot = *_bf_slot_at(set, idx); + + if (!slot) + return false; + + if (_bf_slot_is_live(slot) && set->ops.equal(slot, data, set->ctx)) { + if (index) + *index = idx; + return true; + } + + idx = (idx + 1) % n; + } + + return false; +} + +int bf_hashset_new(bf_hashset **set, const bf_hashset_ops *ops, void *ctx) +{ + _free_bf_hashset_ bf_hashset *_set = NULL; + + assert(set); + assert(ops); + assert(ops->hash); + assert(ops->equal); + + _set = calloc(1, sizeof(*_set)); + if (!_set) + return -ENOMEM; + + bf_hashset_init(_set, ops, ctx); + + *set = TAKE_PTR(_set); + + return 0; +} + +void bf_hashset_free(bf_hashset **set) +{ + assert(set); + + if (!*set) + return; + + bf_hashset_clean(*set); + free(*set); + *set = NULL; +} + +void bf_hashset_init(bf_hashset *set, const bf_hashset_ops *ops, void *ctx) +{ + assert(set); + assert(ops); + assert(ops->hash); + assert(ops->equal); + + set->slots = bf_vector_default(sizeof(void *)); + set->len = 0; + set->n_used = 0; + set->ops = *ops; + set->ctx = ctx; +} + +void bf_hashset_clean(bf_hashset *set) +{ + assert(set); + + if (set->ops.free) { + size_t n = _bf_n_slots(set); + for (size_t i = 0; i < n; ++i) { + void **slot = _bf_slot_at(set, i); + if (_bf_slot_is_live(*slot)) + set->ops.free(slot, set->ctx); + } + } + + bf_vector_clean(&set->slots); + set->len = 0; + set->n_used = 0; +} + +int bf_hashset_add(bf_hashset *set, void *data) +{ + size_t idx; + bool was_tombstone; + int r; + + assert(set); + assert(data); + + if (_bf_hashset_find(set, data, NULL)) + return -EEXIST; + + if (_bf_hashset_needs_grow(set)) { + r = _bf_hashset_grow(set); + if (r) + return r; + } + + idx = _bf_hashset_index(set, data); + + while (_bf_slot_is_live(*_bf_slot_at(set, idx))) + idx = (idx + 1) % _bf_n_slots(set); + + was_tombstone = *_bf_slot_at(set, idx) == _BF_HASHSET_TOMBSTONE; + *_bf_slot_at(set, idx) = data; + ++set->len; + + if (!was_tombstone) + ++set->n_used; + + return 0; +} + +bool bf_hashset_contains(const bf_hashset *set, const void *data) +{ + assert(set); + assert(data); + + return _bf_hashset_find(set, data, NULL); +} + +void *bf_hashset_get(const bf_hashset *set, const void *data) +{ + size_t idx; + + assert(set); + assert(data); + + if (!_bf_hashset_find(set, data, &idx)) + return NULL; + + return *_bf_slot_at(set, idx); +} + +int bf_hashset_remove(bf_hashset *set, const void *data) +{ + size_t idx; + + assert(set); + assert(data); + + if (!_bf_hashset_find(set, data, &idx)) + return 0; + + if (set->ops.free) + set->ops.free(_bf_slot_at(set, idx), set->ctx); + + *_bf_slot_at(set, idx) = _BF_HASHSET_TOMBSTONE; + --set->len; + + return 0; +} diff --git a/src/libbpfilter/include/bpfilter/data_structures/hashset.h b/src/libbpfilter/include/bpfilter/data_structures/hashset.h new file mode 100644 index 000000000..36705d30d --- /dev/null +++ b/src/libbpfilter/include/bpfilter/data_structures/hashset.h @@ -0,0 +1,213 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +/** + * @file hashset.h + * + * Open-addressing hashset with linear probing, backed by @ref bf_vector. + * Each slot is a @c void* pointer. @c NULL means empty, sentinel value 1 + * means tombstone, anything else is a live element. Uses user-provided + * callbacks (with an opaque context pointer) for hashing, comparison, and + * cleanup. + */ + +typedef uint64_t (*bf_hashset_ops_hash)(const void *data, void *ctx); +typedef bool (*bf_hashset_ops_equal)(const void *lhs, const void *rhs, + void *ctx); +typedef void (*bf_hashset_ops_free)(void **data, void *ctx); + +/** + * @struct bf_hashset_ops + * + * @var bf_hashset_ops::hash + * Hash function for an element. Must be non-NULL. + * @var bf_hashset_ops::equal + * Equality comparison for two elements. Must be non-NULL. + * @var bf_hashset_ops::free + * Free callback for an element. If NULL, elements won't be freed. + */ +typedef struct +{ + bf_hashset_ops_hash hash; + bf_hashset_ops_equal equal; + bf_hashset_ops_free free; +} bf_hashset_ops; + +/** + * @struct bf_hashset + * + * @var bf_hashset::slots + * Backing vector of @c void* pointers. + * @var bf_hashset::len + * Number of occupied slots (not counting tombstones). + * @var bf_hashset::n_used + * Number of occupied + tombstone slots (used for load factor). + * @var bf_hashset::ops + * Callbacks for hashing, comparing, and freeing elements. + * @var bf_hashset::ctx + * Opaque context pointer passed to every callback. + */ +typedef struct bf_hashset +{ + struct bf_vector slots; + size_t len; + size_t n_used; + bf_hashset_ops ops; + void *ctx; +} bf_hashset; + +#define _free_bf_hashset_ __attribute__((cleanup(bf_hashset_free))) +#define _clean_bf_hashset_ __attribute__((cleanup(bf_hashset_clean))) + +static inline bool _bf_hashset_slot_is_occupied(void *slot) +{ + return slot != NULL && slot != (void *)1; +} + +/** + * @brief Iterate over all occupied elements in a hashset. + * + * @param set Pointer to the hashset. Must be non-NULL. + * @param elem_var Name of the @c void* variable to hold each element. + */ +#define bf_hashset_foreach(set, elem_var) \ + for (size_t _bf_hset_idx = 0, _bf_hset_brk = 0; \ + _bf_hset_idx < bf_vector_len(&(set)->slots) && !_bf_hset_brk; \ + ++_bf_hset_idx) \ + if (!_bf_hashset_slot_is_occupied( \ + *(void **)bf_vector_get(&(set)->slots, _bf_hset_idx))) \ + continue; \ + else \ + for (void *(elem_var) = \ + (_bf_hset_brk = 1, \ + *(void **)bf_vector_get(&(set)->slots, _bf_hset_idx)); \ + _bf_hset_brk; _bf_hset_brk = 0) + +/** + * @brief Allocate and initialise a new hashset. + * + * @param set Set to allocate and initialise. Can't be NULL. + * @param ops Callbacks for hashing, comparing, and freeing elements. + * @c hash and @c equal must be non-NULL. Can't be NULL. + * @param ctx Opaque context pointer passed to every callback. Can be NULL. + * @return 0 on success, or a negative errno value on failure. + */ +int bf_hashset_new(bf_hashset **set, const bf_hashset_ops *ops, void *ctx); + +/** + * @brief Free a hashset. + * + * @param set Pointer to the hashset pointer. Must be non-NULL. + */ +void bf_hashset_free(bf_hashset **set); + +/** + * @brief Initialise a stack-allocated hashset. + * + * @param set Set to initialise. Can't be NULL. + * @param ops Callbacks for hashing, comparing, and freeing elements. + * @c hash and @c equal must be non-NULL. Can't be NULL. + * @param ctx Opaque context pointer passed to every callback. Can be NULL. + */ +void bf_hashset_init(bf_hashset *set, const bf_hashset_ops *ops, void *ctx); + +/** + * @brief Clean up a hashset, freeing all elements and the backing buffer. + * + * After this call the hashset is empty and can be reused or discarded. + * + * @param set Pointer to the hashset. Must be non-NULL. + */ +void bf_hashset_clean(bf_hashset *set); + +/** + * @brief Get the number of elements in the hashset. + * + * @param set Initialised hashset. Must be non-NULL. + * @return Number of elements stored. + */ +static inline size_t bf_hashset_size(const bf_hashset *set) +{ + assert(set); + return set->len; +} + +/** + * @brief Get the current number of slots. + * + * @param set Initialised hashset. Must be non-NULL. + * @return Current slot count. + */ +static inline size_t bf_hashset_cap(const bf_hashset *set) +{ + assert(set); + return bf_vector_len(&set->slots); +} + +/** + * @brief Check if the hashset is empty. + * + * @param set Initialised hashset. Must be non-NULL. + * @return True if the hashset has no elements. + */ +static inline bool bf_hashset_is_empty(const bf_hashset *set) +{ + assert(set); + return set->len == 0; +} + +/** + * @brief Insert an element into the hashset. + * + * If the element already exists, no duplicate is added and @c -EEXIST is + * returned; ownership of @p data is **not** transferred in that case. + * The hashset grows automatically when the load factor is exceeded. + * On successful insertion, the hashset takes ownership of @p data. + * + * @param set Initialised hashset. Can't be NULL. + * @param data Pointer to store. Can't be NULL. + * @return 0 on success, or a negative errno value on failure. + */ +int bf_hashset_add(bf_hashset *set, void *data); + +/** + * @brief Check whether an element exists in the hashset. + * + * @param set Initialised hashset. Must be non-NULL. + * @param data Element to look up. Must be non-NULL. + * @return True if a matching element is present. + */ +bool bf_hashset_contains(const bf_hashset *set, const void *data); + +/** + * @brief Look up an element in the hashset. + * + * @param set Initialised hashset. Must be non-NULL. + * @param data Element to search for. Must be non-NULL. + * @return Pointer to the stored element, or NULL if not found. + */ +void *bf_hashset_get(const bf_hashset *set, const void *data); + +/** + * @brief Remove an element from the hashset. + * + * The slot is marked as a tombstone. The element is freed using the + * @c free callback if one was provided. Removing an element that doesn't + * exist is a no-op (returns 0). + * + * @param set Initialised hashset. Must be non-NULL. + * @param data Element to remove. Must be non-NULL. + * @return 0 on success, or a negative errno value on failure. + */ +int bf_hashset_remove(bf_hashset *set, const void *data); diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index bcc4c5157..e468bbd4c 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -83,6 +83,7 @@ bf_add_c_test(unit libbpfilter/helper.c) 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/data_structures/hashset.c) bf_add_c_test(unit libbpfilter/data_structures/list.c) bf_add_c_test(unit libbpfilter/logger.c) bf_add_c_test(unit libbpfilter/matcher.c) diff --git a/tests/unit/libbpfilter/data_structures/hashset.c b/tests/unit/libbpfilter/data_structures/hashset.c new file mode 100644 index 000000000..efcfbb73f --- /dev/null +++ b/tests/unit/libbpfilter/data_structures/hashset.c @@ -0,0 +1,370 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. + */ + +#include + +#include +#include + +#include "test.h" + +static uint64_t uint32_hash(const void *data, void *ctx) +{ + (void)ctx; + return bf_fnv1a(data, sizeof(uint32_t), BF_FNV1A_INIT); +} + +static bool uint32_equal(const void *lhs, const void *rhs, void *ctx) +{ + (void)ctx; + return *(const uint32_t *)lhs == *(const uint32_t *)rhs; +} + +static void uint32_free(void **data, void *ctx) +{ + (void)ctx; + freep((void *)data); +} + +static const bf_hashset_ops uint32_ops = { + .hash = uint32_hash, + .equal = uint32_equal, + .free = uint32_free, +}; + +static const bf_hashset_ops uint32_ops_nofree = { + .hash = uint32_hash, + .equal = uint32_equal, + .free = NULL, +}; + +static void new_and_free(void **state) +{ + _free_bf_hashset_ bf_hashset *set = NULL; + + (void)state; + + assert_ok(bf_hashset_new(&set, &uint32_ops, NULL)); + assert_non_null(set); + assert_true(bf_hashset_is_empty(set)); + bf_hashset_free(&set); + assert_null(set); + + // Auto-free via cleanup attribute + assert_ok(bf_hashset_new(&set, &uint32_ops, NULL)); +} + +static void init_and_clean(void **state) +{ + _clean_bf_hashset_ bf_hashset set; + + (void)state; + + bf_hashset_init(&set, &uint32_ops, NULL); + assert_true(bf_hashset_is_empty(&set)); + assert_int_equal(bf_hashset_size(&set), 0); + assert_int_equal(bf_hashset_cap(&set), 0); +} + +static void add_and_contains(void **state) +{ + _clean_bf_hashset_ bf_hashset set; + uint32_t *val; + + (void)state; + + bf_hashset_init(&set, &uint32_ops, NULL); + + val = malloc(sizeof(*val)); + assert_non_null(val); + *val = 42; + + assert_false(bf_hashset_contains(&set, val)); + assert_ok(bf_hashset_add(&set, val)); + assert_int_equal(bf_hashset_size(&set), 1); + assert_true(bf_hashset_contains(&set, val)); +} + +static void add_multiple(void **state) +{ + _clean_bf_hashset_ bf_hashset set; + + (void)state; + + bf_hashset_init(&set, &uint32_ops, NULL); + + for (uint32_t i = 0; i < 5; ++i) { + uint32_t *val = malloc(sizeof(*val)); + assert_non_null(val); + *val = i * 100; + assert_ok(bf_hashset_add(&set, val)); + } + + assert_int_equal(bf_hashset_size(&set), 5); + + for (uint32_t i = 0; i < 5; ++i) { + uint32_t key = i * 100; + assert_true(bf_hashset_contains(&set, &key)); + } +} + +static void add_duplicate(void **state) +{ + _clean_bf_hashset_ bf_hashset set; + uint32_t *val1, *val2; + + (void)state; + + bf_hashset_init(&set, &uint32_ops, NULL); + + val1 = malloc(sizeof(*val1)); + assert_non_null(val1); + *val1 = 42; + + val2 = malloc(sizeof(*val2)); + assert_non_null(val2); + *val2 = 42; + + assert_ok(bf_hashset_add(&set, val1)); + // val2 is a duplicate; hashset returns -EEXIST and doesn't take ownership + assert_int_equal(bf_hashset_add(&set, val2), -EEXIST); + assert_int_equal(bf_hashset_size(&set), 1); + + free(val2); +} + +static void get(void **state) +{ + _clean_bf_hashset_ bf_hashset set; + uint32_t *val; + uint32_t key; + + (void)state; + + bf_hashset_init(&set, &uint32_ops, NULL); + + val = malloc(sizeof(*val)); + assert_non_null(val); + *val = 42; + assert_ok(bf_hashset_add(&set, val)); + + key = 42; + assert_ptr_equal(bf_hashset_get(&set, &key), val); + + key = 99; + assert_null(bf_hashset_get(&set, &key)); +} + +static void remove_elem(void **state) +{ + _clean_bf_hashset_ bf_hashset set; + uint32_t *val1, *val2; + uint32_t key; + + (void)state; + + bf_hashset_init(&set, &uint32_ops, NULL); + + val1 = malloc(sizeof(*val1)); + assert_non_null(val1); + *val1 = 10; + + val2 = malloc(sizeof(*val2)); + assert_non_null(val2); + *val2 = 20; + + assert_ok(bf_hashset_add(&set, val1)); + assert_ok(bf_hashset_add(&set, val2)); + assert_int_equal(bf_hashset_size(&set), 2); + + key = 10; + assert_ok(bf_hashset_remove(&set, &key)); + assert_int_equal(bf_hashset_size(&set), 1); + assert_false(bf_hashset_contains(&set, &key)); + + key = 20; + assert_true(bf_hashset_contains(&set, &key)); + + // Remove nonexistent is a no-op + key = 99; + assert_ok(bf_hashset_remove(&set, &key)); + assert_int_equal(bf_hashset_size(&set), 1); +} + +static void remove_and_readd(void **state) +{ + _clean_bf_hashset_ bf_hashset set; + uint32_t *val, *val2; + uint32_t key; + + (void)state; + + bf_hashset_init(&set, &uint32_ops, NULL); + + val = malloc(sizeof(*val)); + assert_non_null(val); + *val = 42; + assert_ok(bf_hashset_add(&set, val)); + + key = 42; + assert_ok(bf_hashset_remove(&set, &key)); + assert_false(bf_hashset_contains(&set, &key)); + + // Re-add after removal (tombstone reuse) + val2 = malloc(sizeof(*val2)); + assert_non_null(val2); + *val2 = 42; + assert_ok(bf_hashset_add(&set, val2)); + assert_int_equal(bf_hashset_size(&set), 1); + assert_true(bf_hashset_contains(&set, &key)); +} + +static void foreach_basic(void **state) +{ + _clean_bf_hashset_ bf_hashset set; + size_t count; + + (void)state; + + bf_hashset_init(&set, &uint32_ops, NULL); + + // foreach on empty set does nothing + count = 0; + bf_hashset_foreach (&set, elem) { + (void)elem; + ++count; + } + assert_int_equal(count, 0); + + for (uint32_t i = 0; i < 3; ++i) { + uint32_t *val = malloc(sizeof(*val)); + assert_non_null(val); + *val = i + 1; + assert_ok(bf_hashset_add(&set, val)); + } + + count = 0; + bf_hashset_foreach (&set, elem) { + (void)elem; + ++count; + } + assert_int_equal(count, 3); +} + +static void foreach_after_removal(void **state) +{ + _clean_bf_hashset_ bf_hashset set; + size_t count; + + (void)state; + + bf_hashset_init(&set, &uint32_ops, NULL); + + for (uint32_t i = 0; i < 3; ++i) { + uint32_t *val = malloc(sizeof(*val)); + assert_non_null(val); + *val = i + 1; + assert_ok(bf_hashset_add(&set, val)); + } + + uint32_t key = 2; + assert_ok(bf_hashset_remove(&set, &key)); + + // Tombstoned slot must be skipped + count = 0; + bf_hashset_foreach (&set, elem) { + (void)elem; + ++count; + } + assert_int_equal(count, 2); +} + +static void foreach_break(void **state) +{ + _clean_bf_hashset_ bf_hashset set; + size_t count; + + (void)state; + + bf_hashset_init(&set, &uint32_ops, NULL); + + for (uint32_t i = 0; i < 3; ++i) { + uint32_t *val = malloc(sizeof(*val)); + assert_non_null(val); + *val = i + 1; + assert_ok(bf_hashset_add(&set, val)); + } + + count = 0; + bf_hashset_foreach (&set, elem) { + (void)elem; + ++count; + break; + } + assert_int_equal(count, 1); +} + +static void grow(void **state) +{ + _clean_bf_hashset_ bf_hashset set; + + (void)state; + + bf_hashset_init(&set, &uint32_ops, NULL); + + for (uint32_t i = 0; i < 20; ++i) { + uint32_t *val = malloc(sizeof(*val)); + assert_non_null(val); + *val = i; + assert_ok(bf_hashset_add(&set, val)); + } + + assert_int_equal(bf_hashset_size(&set), 20); + assert_true(bf_hashset_cap(&set) > 16); + + for (uint32_t i = 0; i < 20; ++i) { + uint32_t key = i; + assert_true(bf_hashset_contains(&set, &key)); + } +} + +static void nofree_ops(void **state) +{ + _clean_bf_hashset_ bf_hashset set; + uint32_t stack_vals[] = {100, 200, 300}; + + (void)state; + + bf_hashset_init(&set, &uint32_ops_nofree, NULL); + + for (size_t i = 0; i < ARRAY_SIZE(stack_vals); ++i) + assert_ok(bf_hashset_add(&set, &stack_vals[i])); + + assert_int_equal(bf_hashset_size(&set), 3); + assert_true(bf_hashset_contains(&set, &stack_vals[0])); + assert_true(bf_hashset_contains(&set, &stack_vals[1])); + assert_true(bf_hashset_contains(&set, &stack_vals[2])); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(new_and_free), + cmocka_unit_test(init_and_clean), + cmocka_unit_test(add_and_contains), + cmocka_unit_test(add_multiple), + cmocka_unit_test(add_duplicate), + cmocka_unit_test(get), + cmocka_unit_test(remove_elem), + cmocka_unit_test(remove_and_readd), + cmocka_unit_test(foreach_basic), + cmocka_unit_test(foreach_after_removal), + cmocka_unit_test(foreach_break), + cmocka_unit_test(grow), + cmocka_unit_test(nofree_ops), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} From 02938a30811881ab86db6cfdf03d516df7387de4 Mon Sep 17 00:00:00 2001 From: Pawel Zmarzly Date: Thu, 12 Mar 2026 14:00:02 +0000 Subject: [PATCH 6/6] lib: set: use bf_hashset for elems field --- src/libbpfilter/include/bpfilter/set.h | 14 +--- src/libbpfilter/set.c | 99 ++++++++++++-------------- tests/harness/test.c | 9 +-- tests/unit/libbpfilter/set.c | 15 ++-- 4 files changed, 58 insertions(+), 79 deletions(-) diff --git a/src/libbpfilter/include/bpfilter/set.h b/src/libbpfilter/include/bpfilter/set.h index 17f1f804a..6eddc1deb 100644 --- a/src/libbpfilter/include/bpfilter/set.h +++ b/src/libbpfilter/include/bpfilter/set.h @@ -8,8 +8,8 @@ #include #include +#include #include -#include #include #include @@ -54,7 +54,7 @@ struct bf_set size_t n_comps; /** Elements of the set. */ - bf_list elems; + bf_hashset elems; /** Size of a single element. All elements have the same size, it's derived * from the key. */ @@ -73,15 +73,7 @@ struct bf_set * (as `void *`). This variable will be created automatically. */ #define bf_set_foreach(set, elem_var) \ - for (bf_list_node *_bf_set_node = (set)->elems.head, \ - *_bf_set_next = _bf_set_node ? _bf_set_node->next : \ - NULL, \ - *_bf_set_brk = NULL; \ - _bf_set_node; _bf_set_node = _bf_set_brk ? NULL : _bf_set_next, \ - _bf_set_next = _bf_set_node ? _bf_set_node->next : NULL) \ - for (void *(elem_var) = (_bf_set_brk = (void *)1, \ - bf_list_node_get_data(_bf_set_node)); \ - _bf_set_brk; _bf_set_brk = NULL) + bf_hashset_foreach (&(set)->elems, elem_var) /** * @brief Allocate and initialise a new set. diff --git a/src/libbpfilter/set.c b/src/libbpfilter/set.c index 7d4480514..158228064 100644 --- a/src/libbpfilter/set.c +++ b/src/libbpfilter/set.c @@ -11,9 +11,9 @@ #include #include +#include "bpfilter/data_structures/hashset.h" #include "bpfilter/dump.h" #include "bpfilter/helper.h" -#include "bpfilter/data_structures/list.h" #include "bpfilter/logger.h" #include "bpfilter/pack.h" @@ -22,6 +22,28 @@ (BF_FLAGS(BF_MATCHER_IP4_SNET, BF_MATCHER_IP4_DNET, BF_MATCHER_IP6_SNET, \ BF_MATCHER_IP6_DNET)) +static uint64_t _bf_set_elem_hash(const void *data, void *ctx) +{ + return bf_fnv1a(data, *(const size_t *)ctx, BF_FNV1A_INIT); +} + +static bool _bf_set_elem_equal(const void *lhs, const void *rhs, void *ctx) +{ + return memcmp(lhs, rhs, *(const size_t *)ctx) == 0; +} + +static void _bf_set_elem_free(void **data, void *ctx) +{ + (void)ctx; + freep((void *)data); +} + +static const bf_hashset_ops _bf_set_elem_ops = { + .hash = _bf_set_elem_hash, + .equal = _bf_set_elem_equal, + .free = _bf_set_elem_free, +}; + int bf_set_new(struct bf_set **set, const char *name, enum bf_matcher_type *key, size_t n_comps) { @@ -40,7 +62,7 @@ int bf_set_new(struct bf_set **set, const char *name, enum bf_matcher_type *key, BF_SET_MAX_N_COMPS); } - _set = malloc(sizeof(*_set)); + _set = calloc(1, sizeof(*_set)); if (!_set) return -ENOMEM; @@ -54,7 +76,7 @@ int bf_set_new(struct bf_set **set, const char *name, enum bf_matcher_type *key, memcpy(&(_set)->key, key, n_comps * sizeof(enum bf_matcher_type)); _set->n_comps = n_comps; _set->elem_size = 0; - _set->elems = bf_list_default(freep, NULL); + bf_hashset_init(&_set->elems, &_bf_set_elem_ops, &_set->elem_size); for (size_t i = 0; i < n_comps; ++i) { const struct bf_matcher_ops *ops; @@ -194,10 +216,9 @@ int bf_set_add_elem_raw(struct bf_set *set, const char *raw_elem) raw_elem); } - r = bf_list_add_tail(&set->elems, elem); + r = bf_set_add_elem(set, elem); if (r) return bf_err_r(r, "failed to insert element into set"); - TAKE_PTR(elem); return 0; } @@ -325,7 +346,7 @@ void bf_set_free(struct bf_set **set) if (!*set) return; - bf_list_clean(&(*set)->elems); + bf_hashset_clean(&(*set)->elems); freep((void *)&(*set)->name); freep((void *)set); } @@ -373,17 +394,18 @@ void bf_set_dump(const struct bf_set *set, prefix_t *prefix) bf_dump_prefix_pop(prefix); DUMP(prefix, "elem_size: %lu", set->elem_size); - DUMP(bf_dump_prefix_last(prefix), "elems: bf_list[%lu]", + DUMP(bf_dump_prefix_last(prefix), "elems: bf_hashset[%lu]", bf_set_size(set)); bf_dump_prefix_push(prefix); - bf_list_foreach (&set->elems, elem_node) { - if (bf_list_is_tail(&set->elems, elem_node)) + size_t _dump_n = 0; + bf_set_foreach (set, elem) { + if (++_dump_n == bf_set_size(set)) bf_dump_prefix_last(prefix); - DUMP(prefix, "void * @ %p", bf_list_node_get_data(elem_node)); + DUMP(prefix, "void * @ %p", elem); bf_dump_prefix_push(prefix); - bf_dump_hex(prefix, bf_list_node_get_data(elem_node), set->elem_size); + bf_dump_hex(prefix, elem, set->elem_size); bf_dump_prefix_pop(prefix); } bf_dump_prefix_pop(prefix); @@ -394,38 +416,34 @@ void bf_set_dump(const struct bf_set *set, prefix_t *prefix) int bf_set_add_elem(struct bf_set *set, const void *elem) { _cleanup_free_ void *_elem = NULL; - int r; assert(set); assert(elem); + if (bf_hashset_contains(&set->elems, elem)) + return 0; + _elem = malloc(set->elem_size); if (!_elem) return -ENOMEM; memcpy(_elem, elem, set->elem_size); - r = bf_list_add_tail(&set->elems, _elem); - if (r < 0) - return r; - - TAKE_PTR(_elem); - - return 0; + return bf_hashset_add(&set->elems, TAKE_PTR(_elem)); } bool bf_set_is_empty(const struct bf_set *set) { assert(set); - return bf_list_is_empty(&set->elems); + return bf_hashset_is_empty(&set->elems); } size_t bf_set_size(const struct bf_set *set) { assert(set); - return bf_list_size(&set->elems); + return bf_hashset_size(&set->elems); } const char *bf_set_get_name(const struct bf_set *set) @@ -488,28 +506,10 @@ int bf_set_add_many(struct bf_set *dest, struct bf_set **to_add) if (r) return r; - // @todo This has O(n * m) complexity. We could get to O(n log n + m) by - // turning the linked list into an array and sorting it, but we should - // just replace underlying bf_list with true hashset and enjoy O(m). - bf_list_foreach (&(*to_add)->elems, elem_node) { - void *elem_to_add = bf_list_node_get_data(elem_node); - bool found = false; - - bf_set_foreach (dest, dest_elem) { - if (memcmp(dest_elem, elem_to_add, dest->elem_size) == 0) { - found = true; - break; - } - } - - if (!found) { - r = bf_list_add_tail(&dest->elems, - bf_list_node_get_data(elem_node)); - if (r) - return bf_err_r(r, "failed to add element to set"); - // Take ownership of data to stop to_add cleanup from freeing it. - bf_list_node_take_data(elem_node); - } + bf_set_foreach (*to_add, elem) { + r = bf_set_add_elem(dest, elem); + if (r) + return bf_err_r(r, "failed to add element to set"); } bf_set_free(to_add); @@ -529,17 +529,8 @@ int bf_set_remove_many(struct bf_set *dest, struct bf_set **to_remove) if (r) return r; - // @todo This has O(n * m) complexity. Could be O(m) if we used hashsets. - bf_set_foreach (*to_remove, elem_to_remove) { - bf_list_foreach (&dest->elems, dest_elem_node) { - const void *dest_elem = bf_list_node_get_data(dest_elem_node); - - if (memcmp(dest_elem, elem_to_remove, dest->elem_size) == 0) { - bf_list_delete(&dest->elems, dest_elem_node); - break; - } - } - } + bf_set_foreach (*to_remove, elem) + bf_hashset_remove(&dest->elems, elem); bf_set_free(to_remove); diff --git a/tests/harness/test.c b/tests/harness/test.c index 2c9036606..08e176773 100644 --- a/tests/harness/test.c +++ b/tests/harness/test.c @@ -230,19 +230,14 @@ bool bft_counter_eq(const struct bf_counter *lhs, const struct bf_counter *rhs) bool bft_set_eq(const struct bf_set *lhs, const struct bf_set *rhs) { - const struct bf_list_node *n0, *n1; - if (bf_set_size(lhs) != bf_set_size(rhs)) return false; if (lhs->elem_size != rhs->elem_size) return false; - n0 = bf_list_get_head(&lhs->elems); - n1 = bf_list_get_head(&rhs->elems); - for (; n0 || n1; n0 = bf_list_node_next(n0), n1 = bf_list_node_next(n1)) { - if (0 != memcmp(bf_list_node_get_data(n0), bf_list_node_get_data(n1), - lhs->elem_size)) + bf_set_foreach (lhs, elem) { + if (!bf_hashset_contains(&rhs->elems, elem)) return false; } diff --git a/tests/unit/libbpfilter/set.c b/tests/unit/libbpfilter/set.c index 3fadfa132..fa0844f41 100644 --- a/tests/unit/libbpfilter/set.c +++ b/tests/unit/libbpfilter/set.c @@ -281,9 +281,9 @@ static void add_many_basic(void **state) assert_ok(bf_set_add_many(dest, &to_add)); assert_int_equal(bf_set_size(dest), 3); - assert_int_equal(*(uint32_t *)bf_list_get_at(&dest->elems, 0), elem1); - assert_int_equal(*(uint32_t *)bf_list_get_at(&dest->elems, 1), elem2); - assert_int_equal(*(uint32_t *)bf_list_get_at(&dest->elems, 2), elem3); + assert_true(bf_hashset_contains(&dest->elems, &elem1)); + assert_true(bf_hashset_contains(&dest->elems, &elem2)); + assert_true(bf_hashset_contains(&dest->elems, &elem3)); assert_null(to_add); } @@ -348,8 +348,9 @@ static void remove_many_basic(void **state) assert_ok(bf_set_remove_many(dest, &to_remove)); assert_int_equal(bf_set_size(dest), 2); - assert_int_equal(*(uint32_t *)bf_list_get_at(&dest->elems, 0), elem1); - assert_int_equal(*(uint32_t *)bf_list_get_at(&dest->elems, 1), elem3); + assert_true(bf_hashset_contains(&dest->elems, &elem1)); + assert_false(bf_hashset_contains(&dest->elems, &elem2)); + assert_true(bf_hashset_contains(&dest->elems, &elem3)); assert_null(to_remove); } @@ -378,8 +379,8 @@ static void remove_many_disjoint_sets(void **state) assert_ok(bf_set_remove_many(dest, &to_remove)); assert_int_equal(bf_set_size(dest), 2); - assert_int_equal(*(uint32_t *)bf_list_get_at(&dest->elems, 0), elem1); - assert_int_equal(*(uint32_t *)bf_list_get_at(&dest->elems, 1), elem2); + assert_true(bf_hashset_contains(&dest->elems, &elem1)); + assert_true(bf_hashset_contains(&dest->elems, &elem2)); assert_null(to_remove); }