From 0bfb49ed357756815e35ac7a69cd04c4f19b1033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20V=C3=A1rady?= Date: Thu, 14 Nov 2024 10:36:30 +0100 Subject: [PATCH 1/8] adt: add intrusive insertion-ordered map implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: László Várady --- lib/CMakeLists.txt | 4 + lib/Makefile.am | 4 +- lib/adt/CMakeLists.txt | 9 + lib/adt/Makefile.am | 11 ++ lib/adt/iord_map.c | 187 +++++++++++++++++++ lib/adt/iord_map.h | 74 ++++++++ lib/adt/tests/CMakeLists.txt | 1 + lib/adt/tests/Makefile.am | 7 + lib/adt/tests/test_iord_map.c | 332 ++++++++++++++++++++++++++++++++++ tests/copyright/policy | 2 + 10 files changed, 630 insertions(+), 1 deletion(-) create mode 100644 lib/adt/CMakeLists.txt create mode 100644 lib/adt/Makefile.am create mode 100644 lib/adt/iord_map.c create mode 100644 lib/adt/iord_map.h create mode 100644 lib/adt/tests/CMakeLists.txt create mode 100644 lib/adt/tests/Makefile.am create mode 100644 lib/adt/tests/test_iord_map.c diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 050d0a2f2..bcf0e093a 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -38,6 +38,7 @@ add_subdirectory(secret-storage) add_subdirectory(logthrsource) add_subdirectory(logthrdest) add_subdirectory(signal-slot-connector) +add_subdirectory(adt) set(LIB_SUBDIR_HEADERS ${ACK_TRACKER_HEADERS} @@ -66,6 +67,7 @@ set(LIB_SUBDIR_HEADERS ${LOGTHRDEST_HEADERS} ${LOGTHRSOURCE_HEADERS} ${SIGNAL_SLOT_CONNECTOR_HEADERS} + ${ADT_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/filter/filter-expr-grammar.h ${CMAKE_CURRENT_BINARY_DIR}/filterx/filterx-grammar.h ${CMAKE_CURRENT_BINARY_DIR}/rewrite/rewrite-expr-grammar.h @@ -284,6 +286,7 @@ set(LIB_SOURCES ${LOGTHRDEST_SOURCES} ${LOGTHRSOURCE_SOURCES} ${SIGNAL_SLOT_CONNECTOR_SOURCES} + ${ADT_SOURCES} ${PROJECT_BINARY_DIR}/lib/cfg-grammar.c ${PROJECT_BINARY_DIR}/lib/block-ref-grammar.c ${PROJECT_BINARY_DIR}/lib/cfg-lex.c @@ -435,6 +438,7 @@ install(FILES ${CSV_SCANNER_HEADERS} DESTINATION include/syslog-ng/scanner/csv-s install(FILES ${LOGTHRDEST_HEADERS} DESTINATION include/syslog-ng/logthrdest) install(FILES ${LOGTHRSOURCE_HEADERS} DESTINATION include/syslog-ng/logthrsource) install(FILES ${SIGNAL_SLOT_CONNECTOR_HEADERS} DESTINATION include/syslog-ng/signal-slot-connector) +install(FILES ${ADT_HEADERS} DESTINATION include/syslog-ng/adt) set(TOOLS merge-grammar.py diff --git a/lib/Makefile.am b/lib/Makefile.am index 995360761..29bf2e39f 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -29,6 +29,7 @@ include lib/logthrsource/Makefile.am include lib/logthrdest/Makefile.am include lib/signal-slot-connector/Makefile.am include lib/multi-line/Makefile.am +include lib/adt/Makefile.am LSNG_RELEASE = $(shell echo @PACKAGE_VERSION@ | cut -d. -f1,2) @@ -307,7 +308,8 @@ lib_libsyslog_ng_la_SOURCES = \ $(multiline_sources) \ $(logthrsource_sources) \ $(logthrdest_sources) \ - $(signal_slot_connector_sources) + $(signal_slot_connector_sources) \ + $(adt_sources) lib_libsyslog_ng_la_CFLAGS = \ $(AM_CFLAGS) \ diff --git a/lib/adt/CMakeLists.txt b/lib/adt/CMakeLists.txt new file mode 100644 index 000000000..942e910ef --- /dev/null +++ b/lib/adt/CMakeLists.txt @@ -0,0 +1,9 @@ +set(ADT_HEADERS + adt/iord_map.h + PARENT_SCOPE) + +set(ADT_SOURCES + adt/iord_map.c + PARENT_SCOPE) + +add_subdirectory(tests) diff --git a/lib/adt/Makefile.am b/lib/adt/Makefile.am new file mode 100644 index 000000000..0110f853b --- /dev/null +++ b/lib/adt/Makefile.am @@ -0,0 +1,11 @@ +adtincludedir = ${pkgincludedir}/adt + +EXTRA_DIST += lib/adt/CMakeLists.txt + +adtinclude_HEADERS = \ + lib/adt/iord_map.h + +adt_sources = \ + lib/adt/iord_map.c + +include lib/adt/tests/Makefile.am diff --git a/lib/adt/iord_map.c b/lib/adt/iord_map.c new file mode 100644 index 000000000..bc9b8ce4e --- /dev/null +++ b/lib/adt/iord_map.c @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2024 Axoflow + * Copyright (c) 2024 László Várady + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#include "iord_map.h" + +#define iord_map_node_container_of(node, offset) ((guint8 *) (node) - (intptr_t) (offset)) +#define iord_map_node_from_container(container, offset) ((IOrdMapNode *) ((guint8 *) (container) + (intptr_t) (offset))) + +struct _IOrdMap +{ + GHashTable *ht; + IOrdMapNode keys; + IOrdMapNode values; + + GDestroyNotify key_destroy_func; + guint16 key_container_offset; + GDestroyNotify value_destroy_func; + guint16 value_container_offset; +}; + +IOrdMap * +iord_map_new(GHashFunc hash_func, GEqualFunc key_equal_func, + guint16 key_container_offset, guint16 value_container_offset) +{ + return iord_map_new_full(hash_func, key_equal_func, NULL, key_container_offset, NULL, value_container_offset); +} + +IOrdMap * +iord_map_new_full(GHashFunc hash_func, GEqualFunc key_equal_func, + GDestroyNotify key_destroy_func, guint16 key_container_offset, + GDestroyNotify value_destroy_func, guint16 value_container_offset) +{ + IOrdMap *self = g_new0(IOrdMap, 1); + + self->ht = g_hash_table_new_full(hash_func, key_equal_func, key_destroy_func, value_destroy_func); + iord_map_node_init(&self->keys); + iord_map_node_init(&self->values); + self->key_destroy_func = key_destroy_func; + self->key_container_offset = key_container_offset; + self->value_destroy_func = value_destroy_func; + self->value_container_offset = value_container_offset; + + return self; +} + +void +iord_map_free(IOrdMap *self) +{ + g_hash_table_destroy(self->ht); + g_free(self); +} + +gboolean +iord_map_contains(IOrdMap *self, gconstpointer key) +{ + return g_hash_table_contains(self->ht, key); +} + +gboolean +iord_map_foreach(IOrdMap *self, IOrdMapForeachFunc func, gpointer user_data) +{ + for (struct iv_list_head *key = self->keys.next, *value = self->values.next; + key != &self->keys; + key = key->next, value = value->next) + { + gpointer key_container = iord_map_node_container_of(key, self->key_container_offset); + gpointer value_container = iord_map_node_container_of(value, self->value_container_offset); + if (!func(key_container, value_container, user_data)) + return FALSE; + } + + return TRUE; +} + +gboolean +iord_map_insert(IOrdMap *self, gpointer key, gpointer value) +{ + gpointer orig_key, old_value; + if (!g_hash_table_lookup_extended(self->ht, key, &orig_key, &old_value)) + { + iv_list_add_tail(iord_map_node_from_container(key, self->key_container_offset), &self->keys); + goto finish; + } + + iv_list_del(iord_map_node_from_container(orig_key, self->key_container_offset)); + iv_list_del(iord_map_node_from_container(old_value, self->value_container_offset)); + + /* keeps the original key */ + iv_list_add_tail(iord_map_node_from_container(orig_key, self->key_container_offset), &self->keys); + +finish: + iv_list_add_tail(iord_map_node_from_container(value, self->value_container_offset), &self->values); + return g_hash_table_insert(self->ht, key, value); +} + +gboolean +iord_map_prepend(IOrdMap *self, gpointer key, gpointer value) +{ + gpointer orig_key, old_value; + if (!g_hash_table_lookup_extended(self->ht, key, &orig_key, &old_value)) + { + iv_list_add(iord_map_node_from_container(key, self->key_container_offset), &self->keys); + goto finish; + } + + iv_list_del(iord_map_node_from_container(orig_key, self->key_container_offset)); + iv_list_del(iord_map_node_from_container(old_value, self->value_container_offset)); + + /* keeps the original key */ + iv_list_add(iord_map_node_from_container(orig_key, self->key_container_offset), &self->keys); + +finish: + iv_list_add(iord_map_node_from_container(value, self->value_container_offset), &self->values); + return g_hash_table_insert(self->ht, key, value); +} + +gpointer +iord_map_lookup(IOrdMap *self, gconstpointer key) +{ + return g_hash_table_lookup(self->ht, key); +} + +gboolean +iord_map_remove(IOrdMap *self, gconstpointer key) +{ + gpointer key_to_delete, value_to_delete; + if (!g_hash_table_steal_extended(self->ht, key, &key_to_delete, &value_to_delete)) + return FALSE; + + iv_list_del(iord_map_node_from_container(key_to_delete, self->key_container_offset)); + iv_list_del(iord_map_node_from_container(value_to_delete, self->value_container_offset)); + + if (self->key_destroy_func) + self->key_destroy_func(key_to_delete); + + if (self->value_destroy_func) + self->value_destroy_func(value_to_delete); + + return TRUE; +} + +void +iord_map_clear(IOrdMap *self) +{ + g_hash_table_remove_all(self->ht); + iord_map_node_init(&self->keys); + iord_map_node_init(&self->values); +} + +gsize +iord_map_size(IOrdMap *self) +{ + return g_hash_table_size(self->ht); +} + +IOrdMapNode * +iord_map_get_keys(IOrdMap *self) +{ + return &self->keys; +} + +IOrdMapNode * +iord_map_get_values(IOrdMap *self) +{ + return &self->values; +} diff --git a/lib/adt/iord_map.h b/lib/adt/iord_map.h new file mode 100644 index 000000000..031a21bac --- /dev/null +++ b/lib/adt/iord_map.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024 Axoflow + * Copyright (c) 2024 László Várady + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#ifndef ADT_IORD_MAP_H +#define ADT_IORD_MAP_H + +#include +#include + +#include "syslog-ng.h" + +/* + * Intrusive insertion-ordered map + * + * This implementation offers average O(1) complexity for insertion, deletion, + * and lookup operations, while providing O(N) iteration with the preservation + * of insertion order. However, this efficiency comes at the cost of increased + * memory usage. + */ + +typedef struct _IOrdMap IOrdMap; +typedef struct iv_list_head IOrdMapNode; + +typedef gboolean (*IOrdMapForeachFunc)(gpointer key, gpointer value, gpointer user_data); + + +IOrdMap *iord_map_new(GHashFunc hash_func, GEqualFunc key_equal_func, + guint16 key_container_offset, guint16 value_container_offset); +IOrdMap *iord_map_new_full(GHashFunc hash_func, GEqualFunc key_equal_func, + GDestroyNotify key_destroy_func, guint16 key_container_offset, + GDestroyNotify value_destroy_func, guint16 value_container_offset); +void iord_map_free(IOrdMap *self); + +gboolean iord_map_contains(IOrdMap *self, gconstpointer key); + +/* insertion/deletion is not allowed */ +gboolean iord_map_foreach(IOrdMap *self, IOrdMapForeachFunc func, gpointer user_data); + +gboolean iord_map_insert(IOrdMap *self, gpointer key, gpointer value); +gboolean iord_map_prepend(IOrdMap *self, gpointer key, gpointer value); +gpointer iord_map_lookup(IOrdMap *self, gconstpointer key); +gboolean iord_map_remove(IOrdMap *self, gconstpointer key); +void iord_map_clear(IOrdMap *self); +gsize iord_map_size(IOrdMap *self); + +IOrdMapNode *iord_map_get_keys(IOrdMap *self); +IOrdMapNode *iord_map_get_values(IOrdMap *self); + +#define iord_map_node_init(node) INIT_IV_LIST_HEAD(node) +#define iord_map_node_foreach(head, current, next) iv_list_for_each_safe(current, next, head) +#define iord_map_node_entry(node, type, member) iv_list_entry(node, type, member) + +#endif diff --git a/lib/adt/tests/CMakeLists.txt b/lib/adt/tests/CMakeLists.txt new file mode 100644 index 000000000..a854d7b6c --- /dev/null +++ b/lib/adt/tests/CMakeLists.txt @@ -0,0 +1 @@ +add_unit_test(CRITERION TARGET test_iord_map) diff --git a/lib/adt/tests/Makefile.am b/lib/adt/tests/Makefile.am new file mode 100644 index 000000000..1019c84fa --- /dev/null +++ b/lib/adt/tests/Makefile.am @@ -0,0 +1,7 @@ +lib_adt_tests_TESTS = \ + lib/adt/tests/test_iord_map + +check_PROGRAMS += ${lib_adt_tests_TESTS} + +lib_adt_tests_test_iord_map_LDADD = $(TEST_LDADD) +lib_adt_tests_test_iord_map_CFLAGS = $(TEST_CFLAGS) diff --git a/lib/adt/tests/test_iord_map.c b/lib/adt/tests/test_iord_map.c new file mode 100644 index 000000000..8d79f16d0 --- /dev/null +++ b/lib/adt/tests/test_iord_map.c @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2024 Axoflow + * Copyright (c) 2024 László Várady + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#include "adt/iord_map.h" + +#include + +typedef struct _TestKey +{ + const gchar *key; + IOrdMapNode n; +} TestKey; + +typedef struct _TestData +{ + gint a; + gint b; + IOrdMapNode n; + IOrdMapNode n2; + gint c; +} TestData; + +#define assert_value_equals(actual, expected) \ + ({ \ + cr_assert_eq((actual)->a, (expected)->a); \ + cr_assert_eq((actual)->b, (expected)->b); \ + cr_assert_eq((actual)->c, (expected)->c); \ + }) + +static inline gboolean +assert_foreach_func(gpointer k, gpointer v, gpointer user_data) +{ + TestKey *key = k; + TestData *value = v; + + gint *expected = user_data; + + gchar expected_key[32]; + g_snprintf(expected_key, sizeof(expected_key), "key%d", *expected); + cr_assert_str_eq(key->key, expected_key); + cr_assert_eq(value->a, *expected); + + (*expected)++; + + return TRUE; +} + +Test(iord_map, new_map_is_empty) +{ + IOrdMap *map = iord_map_new(g_str_hash, g_str_equal, offsetof(TestKey, n), offsetof(TestData, n)); + + cr_assert_not_null(map); + cr_assert_eq(iord_map_size(map), 0); + + IOrdMapNode *keys = iord_map_get_keys(map); + + IOrdMapNode *current, *next; + iord_map_node_foreach(keys, current, next) + { + cr_assert_fail("empty map should have no keys"); + } + + iord_map_free(map); +} + +Test(iord_map, insert_lookup_and_contains) +{ + IOrdMap *map = iord_map_new(g_str_hash, g_str_equal, offsetof(TestKey, n), offsetof(TestData, n)); + + TestKey key = { "key" }; + TestData value = { .a = 1, .b = 2, .c = 3 }; + TestData value2 = { .a = 4, .b = 5, .c = 6 }; + + cr_assert(iord_map_insert(map, &key, &value)); + g_assert(iord_map_contains(map, &key)); + TestData *actual = iord_map_lookup(map, &key); + assert_value_equals(actual, &value); + + cr_assert_not(iord_map_insert(map, &key, &value2)); + actual = iord_map_lookup(map, &key); + assert_value_equals(actual, &value2); + + cr_assert_eq(iord_map_size(map), 1); + + iord_map_free(map); +} + +Test(iord_map, remove) +{ + IOrdMap *map = iord_map_new(g_str_hash, g_str_equal, offsetof(TestKey, n), offsetof(TestData, n)); + + TestKey key = { "key" }; + TestData value = { .a = 1, .b = 2, .c = 3 }; + + cr_assert_not(iord_map_remove(map, &key)); + + iord_map_insert(map, &key, &value); + cr_assert(iord_map_remove(map, &key)); + + iord_map_free(map); +} + +Test(iord_map, clear) +{ + IOrdMap *map = iord_map_new(g_str_hash, g_str_equal, offsetof(TestKey, n), offsetof(TestData, n)); + + TestKey key = { "key" }; + TestKey key2 = { "key2" }; + TestData value = { .a = 1, .b = 2, .c = 3 }; + TestData value2 = { .a = 4, .b = 5, .c = 6 }; + + iord_map_insert(map, &key, &value); + iord_map_insert(map, &key2, &value2); + + iord_map_clear(map); + + cr_assert_eq(iord_map_size(map), 0); + cr_assert_not(iord_map_contains(map, &key)); + cr_assert_not(iord_map_contains(map, &key2)); + + IOrdMapNode *keys = iord_map_get_keys(map); + IOrdMapNode *current, *next; + iord_map_node_foreach(keys, current, next) + { + cr_assert_fail("map should be empty"); + } + + iord_map_free(map); +} + +Test(iord_map, insertion_order_is_preserved) +{ + IOrdMap *map = iord_map_new(g_str_hash, g_str_equal, offsetof(TestKey, n), offsetof(TestData, n)); + + TestKey expected_keys[] = {{"key1"}, {"key2"}, {"key3"}, {"key4"}, {"key5"}}; + TestData expected_values[] = {{1}, {2}, {3}, {4}, {5}}; + + for (gint i = 0; i < G_N_ELEMENTS(expected_keys); i++) + { + iord_map_insert(map, &expected_keys[i], &expected_values[i]); + } + + IOrdMapNode *current, *next; + IOrdMapNode *keys = iord_map_get_keys(map); + gint i = 0; + iord_map_node_foreach(keys, current, next) + { + TestKey *current_key = iord_map_node_entry(current, TestKey, n); + cr_assert_str_eq(current_key->key, expected_keys[i].key); + i++; + } + cr_assert_eq(i, G_N_ELEMENTS(expected_keys)); + + IOrdMapNode *values = iord_map_get_values(map); + i = 0; + iord_map_node_foreach(values, current, next) + { + TestData *current_value = iord_map_node_entry(current, TestData, n); + cr_assert_eq(current_value->a, expected_values[i].a); + i++; + } + cr_assert_eq(i, G_N_ELEMENTS(expected_values)); + + gint idx = 1; + iord_map_foreach(map, assert_foreach_func, &idx); + cr_assert_eq(idx, 6); + + iord_map_free(map); +} + +Test(iord_map, reinsert_moves_key_to_end) +{ + IOrdMap *map = iord_map_new(g_str_hash, g_str_equal, offsetof(TestKey, n), offsetof(TestData, n)); + + TestKey keys[] = {{"key1"}, {"key2"}, {"key3"}, {"key4"}, {"key5"}}; + TestData values[] = {{1}, {2}, {3}, {4}, {5}}; + TestData new_value = {9}; + + for (gint i = 0; i < G_N_ELEMENTS(keys); i++) + { + iord_map_insert(map, &keys[i], &values[i]); + } + + iord_map_insert(map, &keys[0], &new_value); + + IOrdMapNode *actual_keys = iord_map_get_keys(map); + + /* first element*/ + cr_assert_str_eq(iord_map_node_entry(actual_keys->next, TestKey, n)->key, "key2"); + + /* last element */ + cr_assert_str_eq(iord_map_node_entry(actual_keys->prev, TestKey, n)->key, "key1"); + + iord_map_free(map); +} + +Test(iord_map, prepend) +{ + IOrdMap *map = iord_map_new(g_str_hash, g_str_equal, offsetof(TestKey, n), offsetof(TestData, n)); + + TestKey keys[] = {{"key1"}, {"key2"}, {"key3"}, {"key4"}, {"key5"}}; + TestData expected_values[] = {{1}, {2}, {3}, {4}, {5}}; + + for (gint i = 0; i < G_N_ELEMENTS(keys); i++) + { + iord_map_prepend(map, &keys[i], &expected_values[i]); + } + + IOrdMapNode *values = iord_map_get_values(map); + gint i = G_N_ELEMENTS(keys); + IOrdMapNode *current, *next; + iord_map_node_foreach(values, current, next) + { + TestData *current_value = iord_map_node_entry(current, TestData, n); + cr_assert_eq(current_value->a, expected_values[i-1].a); + i--; + } + + cr_assert_eq(i, 0); + + iord_map_free(map); +} + +gint destruct_key_called = 0; +static inline void +destruct_key(gpointer data) +{ + destruct_key_called++; +} + +gint destruct_value_called = 0; +static inline void +destruct_value(gpointer data) +{ + destruct_value_called++; +} + +Test(iord_map, destructors_are_called) +{ + IOrdMap *map = iord_map_new_full(g_str_hash, g_str_equal, destruct_key, offsetof(TestKey, n), + destruct_value, offsetof(TestData, n)); + + TestKey key = { "key" }; + TestKey key2 = { "key2" }; + TestData value = { .a = 1, .b = 2, .c = 3 }; + TestData value2 = { .a = 4, .b = 5, .c = 6 }; + + TestKey same_key = { "key" }; + TestData new_value = { .a = 4, .b = 5, .c = 6 }; + + iord_map_insert(map, &key, &value); + iord_map_insert(map, &key2, &value2); + + iord_map_insert(map, &same_key, &new_value); + + cr_assert_eq(destruct_key_called, 1); + cr_assert_eq(destruct_value_called, 1); + + iord_map_clear(map); + + cr_assert_eq(destruct_key_called, 3); + cr_assert_eq(destruct_value_called, 3); + + iord_map_free(map); +} + +Test(iord_map, multi_map_node) +{ + IOrdMap *map = iord_map_new(g_str_hash, g_str_equal, offsetof(TestKey, n), offsetof(TestData, n)); + IOrdMap *map2 = iord_map_new(g_str_hash, g_str_equal, offsetof(TestKey, n), offsetof(TestData, n2)); + + TestKey key = {"key1"}; + TestKey key2 = {"key2"}; + TestKey key3 = {"key3"}; + TestData value = {0}; + TestData multi_node_data = { .a = 1, .b = 2, .c = 3 }; + + iord_map_insert(map, &key, &multi_node_data); + iord_map_insert(map, &key2, &value); + iord_map_insert(map2, &key3, &multi_node_data); + + TestData expected_values[] = {multi_node_data, value}; + + IOrdMapNode *values = iord_map_get_values(map); + IOrdMapNode *current, *next; + gint i = 0; + iord_map_node_foreach(values, current, next) + { + TestData *current_value = iord_map_node_entry(current, TestData, n); + cr_assert_eq(current_value->a, expected_values[i].a); + i++; + } + cr_assert_eq(i, G_N_ELEMENTS(expected_values)); + + + values = iord_map_get_values(map2); + i = 0; + iord_map_node_foreach(values, current, next) + { + cr_assert_eq(i, 0); + + TestData *current_value = iord_map_node_entry(current, TestData, n2); + cr_assert_eq(current_value->a, 1); + cr_assert_eq(current_value->b, 2); + cr_assert_eq(current_value->c, 3); + i++; + } + + iord_map_free(map2); + iord_map_free(map); +} diff --git a/tests/copyright/policy b/tests/copyright/policy index b15229128..52e435b39 100644 --- a/tests/copyright/policy +++ b/tests/copyright/policy @@ -81,6 +81,8 @@ modules/secure-logging/tests modules/secure-logging/slogencrypt modules/secure-logging/slogverify modules/secure-logging/slogkey +lib/adt +lib/adt/tests lib/compat/cpp-(start|end).\h$ lib/compat/curl.\h$ lib/compat/getline.\c$ From 9972f14141c90aa146eeb1cd1cd420f21e4c1a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20V=C3=A1rady?= Date: Thu, 14 Nov 2024 16:36:25 +0100 Subject: [PATCH 2/8] adt: add intrusive list wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: László Várady --- lib/adt/CMakeLists.txt | 1 + lib/adt/Makefile.am | 3 +- lib/adt/ilist.h | 86 +++++++++++++++++++++++++++ lib/adt/tests/CMakeLists.txt | 1 + lib/adt/tests/Makefile.am | 6 +- lib/adt/tests/test_ilist.c | 111 +++++++++++++++++++++++++++++++++++ 6 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 lib/adt/ilist.h create mode 100644 lib/adt/tests/test_ilist.c diff --git a/lib/adt/CMakeLists.txt b/lib/adt/CMakeLists.txt index 942e910ef..0464d74e0 100644 --- a/lib/adt/CMakeLists.txt +++ b/lib/adt/CMakeLists.txt @@ -1,5 +1,6 @@ set(ADT_HEADERS adt/iord_map.h + adt/ilist.h PARENT_SCOPE) set(ADT_SOURCES diff --git a/lib/adt/Makefile.am b/lib/adt/Makefile.am index 0110f853b..fd33f8436 100644 --- a/lib/adt/Makefile.am +++ b/lib/adt/Makefile.am @@ -3,7 +3,8 @@ adtincludedir = ${pkgincludedir}/adt EXTRA_DIST += lib/adt/CMakeLists.txt adtinclude_HEADERS = \ - lib/adt/iord_map.h + lib/adt/iord_map.h \ + lib/adt/ilist.h adt_sources = \ lib/adt/iord_map.c diff --git a/lib/adt/ilist.h b/lib/adt/ilist.h new file mode 100644 index 000000000..bb2de1536 --- /dev/null +++ b/lib/adt/ilist.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024 Axoflow + * Copyright (c) 2024 László Várady + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#ifndef ADT_ILIST +#define ADT_ILIST + +#include +#include + +/* + * Intrusive list (just a wrapper around iv_list for easier use) + */ + +typedef struct iv_list_head IList; +typedef struct iv_list_head IListNode; + +#define ilist_init(self) INIT_IV_LIST_HEAD(self) +#define ilist_push_back(self, node) iv_list_add_tail(node, self) +#define ilist_push_front(self, node) iv_list_add(node, self) +#define ilist_splice_back(self, list) iv_list_splice_tail(list, self) +#define ilist_splice_front(self, list) iv_list_splice(list, self) +#define ilist_remove(node) iv_list_del(node) +#define ilist_is_empty(self) iv_list_empty(self) +#define ilist_foreach(self, current, next) iv_list_for_each_safe(current, next, self) +#define ilist_entry(self, type, member) iv_list_entry(self, type, member) + +static inline IList * +ilist_pop_back(IList *self) +{ + IList *e = self->prev; + iv_list_del(e); + return e; +} + +static inline IList * +ilist_pop_front(IList *self) +{ + IList *e = self->next; + iv_list_del(e); + return e; +} + +static inline void +_ilist_reverse_node_links(IList *node) +{ + IList *orig_next = node->next; + node->next = node->prev; + node->prev = orig_next; +} + +static inline void +ilist_reverse(IList *self) +{ + IList *current = self->next; + + while (current != self) + { + _ilist_reverse_node_links(current); + current = current->prev; + } + + _ilist_reverse_node_links(self); +} + +#endif diff --git a/lib/adt/tests/CMakeLists.txt b/lib/adt/tests/CMakeLists.txt index a854d7b6c..b8e246c2e 100644 --- a/lib/adt/tests/CMakeLists.txt +++ b/lib/adt/tests/CMakeLists.txt @@ -1 +1,2 @@ add_unit_test(CRITERION TARGET test_iord_map) +add_unit_test(CRITERION TARGET test_ilist) diff --git a/lib/adt/tests/Makefile.am b/lib/adt/tests/Makefile.am index 1019c84fa..c296a14ac 100644 --- a/lib/adt/tests/Makefile.am +++ b/lib/adt/tests/Makefile.am @@ -1,7 +1,11 @@ lib_adt_tests_TESTS = \ - lib/adt/tests/test_iord_map + lib/adt/tests/test_iord_map \ + lib/adt/tests/test_ilist check_PROGRAMS += ${lib_adt_tests_TESTS} lib_adt_tests_test_iord_map_LDADD = $(TEST_LDADD) lib_adt_tests_test_iord_map_CFLAGS = $(TEST_CFLAGS) + +lib_adt_tests_test_ilist_LDADD = $(TEST_LDADD) +lib_adt_tests_test_ilist_CFLAGS = $(TEST_CFLAGS) diff --git a/lib/adt/tests/test_ilist.c b/lib/adt/tests/test_ilist.c new file mode 100644 index 000000000..d07e16611 --- /dev/null +++ b/lib/adt/tests/test_ilist.c @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2024 Axoflow + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#include "adt/ilist.h" +#include + +typedef struct _TestNode +{ + int value; + IListNode node; +} TestData; + +Test(ilist, init) +{ + IList list; + ilist_init(&list); + cr_assert(ilist_is_empty(&list)); +} + +Test(ilist, pop_back) +{ + IList list; + ilist_init(&list); + + TestData e1 = { .value = 1 }; + TestData e2 = { .value = 2 }; + + ilist_push_back(&list, &e1.node); + ilist_push_back(&list, &e2.node); + + IList *popped = ilist_pop_back(&list); + cr_assert_eq(ilist_entry(popped, TestData, node)->value, 2); + cr_assert_eq(ilist_entry(list.prev, TestData, node)->value, 1); +} + +Test(ilist, pop_front) +{ + IList list; + ilist_init(&list); + + TestData e1 = { .value = 1 }; + TestData e2 = { .value = 2 }; + + ilist_push_back(&list, &e1.node); + ilist_push_back(&list, &e2.node); + + IList *popped = ilist_pop_front(&list); + cr_assert_eq(ilist_entry(popped, TestData, node)->value, 1); + cr_assert_eq(ilist_entry(list.next, TestData, node)->value, 2); +} + +Test(ilist, reverse) +{ + IList list; + ilist_init(&list); + + TestData e1 = { .value = 1 }; + TestData e2 = { .value = 2 }; + TestData e3 = { .value = 3 }; + + ilist_push_back(&list, &e1.node); + ilist_push_back(&list, &e2.node); + ilist_push_back(&list, &e3.node); + + ilist_reverse(&list); + + cr_assert_eq(ilist_entry(list.next, TestData, node)->value, 3); + cr_assert_eq(ilist_entry(list.next->next, TestData, node)->value, 2); + cr_assert_eq(ilist_entry(list.prev, TestData, node)->value, 1); +} + +Test(ilist, splice_back) +{ + IList list1, list2; + ilist_init(&list1); + ilist_init(&list2); + + TestData e1 = { .value = 1 }; + TestData e2 = { .value = 2 }; + TestData e3 = { .value = 3 }; + + ilist_push_back(&list1, &e1.node); + ilist_push_back(&list2, &e2.node); + ilist_push_back(&list2, &e3.node); + + ilist_splice_back(&list1, &list2); + + cr_assert_eq(ilist_entry(list1.next, TestData, node)->value, 1); + cr_assert_eq(ilist_entry(list1.next->next, TestData, node)->value, 2); + cr_assert_eq(ilist_entry(list1.prev, TestData, node)->value, 3); +} From 48b0ab9adc52db8fb111c6173f4488967251f083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20V=C3=A1rady?= Date: Sun, 17 Nov 2024 23:51:02 +0100 Subject: [PATCH 3/8] filterx-ref: add intrusive map node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: László Várady --- lib/filterx/filterx-ref.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/filterx/filterx-ref.h b/lib/filterx/filterx-ref.h index 9e68cfe72..7b6b5c4e4 100644 --- a/lib/filterx/filterx-ref.h +++ b/lib/filterx/filterx-ref.h @@ -25,6 +25,7 @@ #define FILTERX_REF_H #include "filterx/filterx-object.h" +#include "adt/iord_map.h" /* * References are currently not part of the FilterX language (hopefully, they @@ -45,6 +46,7 @@ typedef struct _FilterXRef { FilterXObject super; FilterXObject *value; + IOrdMapNode n; } FilterXRef; From 9c7ee994f133b2c58ded12abaf4a14f93b6b6d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20V=C3=A1rady?= Date: Mon, 18 Nov 2024 15:50:17 +0100 Subject: [PATCH 4/8] iord-map: allow in-place construction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: László Várady --- lib/adt/iord_map.c | 45 ++++++++++++++++++++++++++++----------------- lib/adt/iord_map.h | 20 +++++++++++++++++++- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/lib/adt/iord_map.c b/lib/adt/iord_map.c index bc9b8ce4e..50dc3579a 100644 --- a/lib/adt/iord_map.c +++ b/lib/adt/iord_map.c @@ -27,17 +27,33 @@ #define iord_map_node_container_of(node, offset) ((guint8 *) (node) - (intptr_t) (offset)) #define iord_map_node_from_container(container, offset) ((IOrdMapNode *) ((guint8 *) (container) + (intptr_t) (offset))) -struct _IOrdMap + +void +iord_map_init(IOrdMap *self, GHashFunc hash_func, GEqualFunc key_equal_func, + guint16 key_container_offset, guint16 value_container_offset) { - GHashTable *ht; - IOrdMapNode keys; - IOrdMapNode values; + iord_map_init_full(self, hash_func, key_equal_func, NULL, key_container_offset, NULL, value_container_offset); +} + +void +iord_map_init_full(IOrdMap *self, GHashFunc hash_func, GEqualFunc key_equal_func, + GDestroyNotify key_destroy_func, guint16 key_container_offset, + GDestroyNotify value_destroy_func, guint16 value_container_offset) +{ + self->ht = g_hash_table_new_full(hash_func, key_equal_func, key_destroy_func, value_destroy_func); + iord_map_node_init(&self->keys); + iord_map_node_init(&self->values); + self->key_destroy_func = key_destroy_func; + self->key_container_offset = key_container_offset; + self->value_destroy_func = value_destroy_func; + self->value_container_offset = value_container_offset; +} - GDestroyNotify key_destroy_func; - guint16 key_container_offset; - GDestroyNotify value_destroy_func; - guint16 value_container_offset; -}; +void +iord_map_destroy(IOrdMap *self) +{ + g_hash_table_destroy(self->ht); +} IOrdMap * iord_map_new(GHashFunc hash_func, GEqualFunc key_equal_func, @@ -53,13 +69,8 @@ iord_map_new_full(GHashFunc hash_func, GEqualFunc key_equal_func, { IOrdMap *self = g_new0(IOrdMap, 1); - self->ht = g_hash_table_new_full(hash_func, key_equal_func, key_destroy_func, value_destroy_func); - iord_map_node_init(&self->keys); - iord_map_node_init(&self->values); - self->key_destroy_func = key_destroy_func; - self->key_container_offset = key_container_offset; - self->value_destroy_func = value_destroy_func; - self->value_container_offset = value_container_offset; + iord_map_init_full(self, hash_func, key_equal_func, key_destroy_func, key_container_offset, + value_destroy_func, value_container_offset); return self; } @@ -67,7 +78,7 @@ iord_map_new_full(GHashFunc hash_func, GEqualFunc key_equal_func, void iord_map_free(IOrdMap *self) { - g_hash_table_destroy(self->ht); + iord_map_destroy(self); g_free(self); } diff --git a/lib/adt/iord_map.h b/lib/adt/iord_map.h index 031a21bac..461f6d05e 100644 --- a/lib/adt/iord_map.h +++ b/lib/adt/iord_map.h @@ -39,9 +39,20 @@ * memory usage. */ -typedef struct _IOrdMap IOrdMap; typedef struct iv_list_head IOrdMapNode; +typedef struct _IOrdMap +{ + GHashTable *ht; + IOrdMapNode keys; + IOrdMapNode values; + + GDestroyNotify key_destroy_func; + guint16 key_container_offset; + GDestroyNotify value_destroy_func; + guint16 value_container_offset; +} IOrdMap; + typedef gboolean (*IOrdMapForeachFunc)(gpointer key, gpointer value, gpointer user_data); @@ -52,6 +63,13 @@ IOrdMap *iord_map_new_full(GHashFunc hash_func, GEqualFunc key_equal_func, GDestroyNotify value_destroy_func, guint16 value_container_offset); void iord_map_free(IOrdMap *self); +void iord_map_init(IOrdMap *self, GHashFunc hash_func, GEqualFunc key_equal_func, + guint16 key_container_offset, guint16 value_container_offset); +void iord_map_init_full(IOrdMap *self, GHashFunc hash_func, GEqualFunc key_equal_func, + GDestroyNotify key_destroy_func, guint16 key_container_offset, + GDestroyNotify value_destroy_func, guint16 value_container_offset); +void iord_map_destroy(IOrdMap *self); + gboolean iord_map_contains(IOrdMap *self, gconstpointer key); /* insertion/deletion is not allowed */ From 3b42dc01880d8d31bf472f22c93df16603fde7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20V=C3=A1rady?= Date: Mon, 18 Nov 2024 19:29:48 +0100 Subject: [PATCH 5/8] iord-map: detect node relinking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: László Várady --- lib/adt/iord_map.c | 10 ++++++++-- lib/adt/tests/test_iord_map.c | 6 ++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/adt/iord_map.c b/lib/adt/iord_map.c index 50dc3579a..b09341748 100644 --- a/lib/adt/iord_map.c +++ b/lib/adt/iord_map.c @@ -107,10 +107,13 @@ iord_map_foreach(IOrdMap *self, IOrdMapForeachFunc func, gpointer user_data) gboolean iord_map_insert(IOrdMap *self, gpointer key, gpointer value) { + IOrdMapNode *key_node = iord_map_node_from_container(key, self->key_container_offset); + g_assert(!key_node->next || iv_list_empty(key_node)); + gpointer orig_key, old_value; if (!g_hash_table_lookup_extended(self->ht, key, &orig_key, &old_value)) { - iv_list_add_tail(iord_map_node_from_container(key, self->key_container_offset), &self->keys); + iv_list_add_tail(key_node, &self->keys); goto finish; } @@ -128,10 +131,13 @@ iord_map_insert(IOrdMap *self, gpointer key, gpointer value) gboolean iord_map_prepend(IOrdMap *self, gpointer key, gpointer value) { + IOrdMapNode *key_node = iord_map_node_from_container(key, self->key_container_offset); + g_assert(!key_node->next || iv_list_empty(key_node)); + gpointer orig_key, old_value; if (!g_hash_table_lookup_extended(self->ht, key, &orig_key, &old_value)) { - iv_list_add(iord_map_node_from_container(key, self->key_container_offset), &self->keys); + iv_list_add(key_node, &self->keys); goto finish; } diff --git a/lib/adt/tests/test_iord_map.c b/lib/adt/tests/test_iord_map.c index 8d79f16d0..ac1c2b134 100644 --- a/lib/adt/tests/test_iord_map.c +++ b/lib/adt/tests/test_iord_map.c @@ -89,6 +89,7 @@ Test(iord_map, insert_lookup_and_contains) IOrdMap *map = iord_map_new(g_str_hash, g_str_equal, offsetof(TestKey, n), offsetof(TestData, n)); TestKey key = { "key" }; + TestKey key_same = { "key" }; TestData value = { .a = 1, .b = 2, .c = 3 }; TestData value2 = { .a = 4, .b = 5, .c = 6 }; @@ -97,7 +98,7 @@ Test(iord_map, insert_lookup_and_contains) TestData *actual = iord_map_lookup(map, &key); assert_value_equals(actual, &value); - cr_assert_not(iord_map_insert(map, &key, &value2)); + cr_assert_not(iord_map_insert(map, &key_same, &value2)); actual = iord_map_lookup(map, &key); assert_value_equals(actual, &value2); @@ -194,6 +195,7 @@ Test(iord_map, reinsert_moves_key_to_end) IOrdMap *map = iord_map_new(g_str_hash, g_str_equal, offsetof(TestKey, n), offsetof(TestData, n)); TestKey keys[] = {{"key1"}, {"key2"}, {"key3"}, {"key4"}, {"key5"}}; + TestKey keys_reinsert = {"key1"}; TestData values[] = {{1}, {2}, {3}, {4}, {5}}; TestData new_value = {9}; @@ -202,7 +204,7 @@ Test(iord_map, reinsert_moves_key_to_end) iord_map_insert(map, &keys[i], &values[i]); } - iord_map_insert(map, &keys[0], &new_value); + iord_map_insert(map, &keys_reinsert, &new_value); IOrdMapNode *actual_keys = iord_map_get_keys(map); From c3b970fb5a9a568a64ec1807096879bb75240c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20V=C3=A1rady?= Date: Sun, 17 Nov 2024 23:51:44 +0100 Subject: [PATCH 6/8] filterx: add skeleton for native FilterXDict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: László Várady --- lib/filterx/CMakeLists.txt | 2 + lib/filterx/Makefile.am | 2 + lib/filterx/filterx-globals.c | 3 + lib/filterx/object-dict.c | 224 ++++++++++++++++++++++++++++++++++ lib/filterx/object-dict.h | 37 ++++++ 5 files changed, 268 insertions(+) create mode 100644 lib/filterx/object-dict.c create mode 100644 lib/filterx/object-dict.h diff --git a/lib/filterx/CMakeLists.txt b/lib/filterx/CMakeLists.txt index 149c24aad..75889b8b5 100644 --- a/lib/filterx/CMakeLists.txt +++ b/lib/filterx/CMakeLists.txt @@ -48,6 +48,7 @@ set(FILTERX_HEADERS filterx/func-vars.h filterx/object-datetime.h filterx/object-dict-interface.h + filterx/object-dict.h filterx/object-extractor.h filterx/object-json-internal.h filterx/object-json.h @@ -110,6 +111,7 @@ set(FILTERX_SOURCES filterx/func-vars.c filterx/object-datetime.c filterx/object-dict-interface.c + filterx/object-dict.c filterx/object-extractor.c filterx/object-json-array.c filterx/object-json-object.c diff --git a/lib/filterx/Makefile.am b/lib/filterx/Makefile.am index ef782d470..7bde9f4e1 100644 --- a/lib/filterx/Makefile.am +++ b/lib/filterx/Makefile.am @@ -51,6 +51,7 @@ filterxinclude_HEADERS = \ lib/filterx/func-vars.h \ lib/filterx/object-datetime.h \ lib/filterx/object-dict-interface.h \ + lib/filterx/object-dict.h \ lib/filterx/object-extractor.h \ lib/filterx/object-json-internal.h \ lib/filterx/object-json.h \ @@ -112,6 +113,7 @@ filterx_sources = \ lib/filterx/func-vars.c \ lib/filterx/object-datetime.c \ lib/filterx/object-dict-interface.c \ + lib/filterx/object-dict.c \ lib/filterx/object-extractor.c \ lib/filterx/object-json-array.c \ lib/filterx/object-json-object.c \ diff --git a/lib/filterx/filterx-globals.c b/lib/filterx/filterx-globals.c index e53eecac2..11170b33d 100644 --- a/lib/filterx/filterx-globals.c +++ b/lib/filterx/filterx-globals.c @@ -26,6 +26,7 @@ #include "filterx/object-null.h" #include "filterx/object-string.h" #include "filterx/object-json.h" +#include "filterx/object-dict.h" #include "filterx/object-datetime.h" #include "filterx/object-message-value.h" #include "filterx/object-list-interface.h" @@ -88,6 +89,7 @@ static void _simple_init(void) { filterx_builtin_simple_functions_init_private(&filterx_builtin_simple_functions); + g_assert(filterx_builtin_simple_function_register("dict", filterx_dict_new_from_args)); g_assert(filterx_builtin_simple_function_register("json", filterx_json_new_from_args)); g_assert(filterx_builtin_simple_function_register("json_array", filterx_json_array_new_from_args)); g_assert(filterx_builtin_simple_function_register("datetime", filterx_typecast_datetime)); @@ -237,6 +239,7 @@ filterx_global_init(void) filterx_type_init(&FILTERX_TYPE_NAME(list)); filterx_type_init(&FILTERX_TYPE_NAME(dict)); + filterx_type_init(&FILTERX_TYPE_NAME(dictobj)); filterx_type_init(&FILTERX_TYPE_NAME(null)); filterx_type_init(&FILTERX_TYPE_NAME(integer)); diff --git a/lib/filterx/object-dict.c b/lib/filterx/object-dict.c new file mode 100644 index 000000000..c2d7640bf --- /dev/null +++ b/lib/filterx/object-dict.c @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2024 Axoflow + * Copyright (c) 2024 László Várady + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#include "filterx/object-dict.h" +#include "filterx/object-dict-interface.h" +#include "filterx/object-extractor.h" +#include "filterx/object-json.h" +#include "filterx/filterx-object.h" +#include "filterx/filterx-object-istype.h" +#include "filterx/filterx-ref.h" +#include "filterx/filterx-eval.h" + +#include "syslog-ng.h" +#include "str-utils.h" +#include "adt/iord_map.h" + +typedef struct _FilterXDictKey +{ + FilterXObject *key_obj; + IOrdMapNode n; + guint8 in_cache:1; +} FilterXDictKey; + +/* + * FilterXDictValue is either a FilterXRef or an immutable FilterXObject. + * The union and the cache are used to avoid unnecessary allocations. + * + * - FilterXDictValue can be casted to FilterXRef (check _filterx_dict_value_is_ref()) and vice versa. + * - A FilterXRef is always owned and never cached. + * - An immutable FilterXObject is never owned and may be cached. + * + * These are important because certain preconditions must be met due to the intrusive IOrdMapNode and the cache. + */ +typedef union _FilterXDictValue +{ + FilterXRef ref; + struct + { + FilterXObject __null_pad; + FilterXObject *val; + IOrdMapNode n; + guint8 in_cache:1; + } immutable; +} FilterXDictValue; + +G_STATIC_ASSERT(offsetof(FilterXRef, n) == offsetof(FilterXDictValue, immutable.n)); +G_STATIC_ASSERT(offsetof(FilterXDictValue, ref) == offsetof(FilterXDictValue, immutable.__null_pad)); +G_STATIC_ASSERT(offsetof(FilterXDictValue, ref) == 0); + +#define CACHE_SIZE 32 + +struct _FilterXDictObj +{ + FilterXDict super; + IOrdMap map; + + FilterXDictKey key_cache[CACHE_SIZE]; + FilterXDictValue value_cache[CACHE_SIZE]; + guint8 key_cache_len; + guint8 value_cache_len; +}; + +static gboolean +_filterx_dict_truthy(FilterXObject *s) +{ + return TRUE; +} + +static gboolean +_filterx_dict_marshal(FilterXObject *s, GString *repr, LogMessageValueType *t) +{ + return TRUE; +} + +static gboolean +_filterx_dict_repr(FilterXObject *s, GString *repr) +{ + return TRUE; +} + +static FilterXObject * +_filterx_dict_clone(FilterXObject *s) +{ + return NULL; +} + +static FilterXObject * +_filterx_dict_get_subscript(FilterXDict *s, FilterXObject *key) +{ + return NULL; +} + +static gboolean +_filterx_dict_set_subscript(FilterXDict *s, FilterXObject *key, FilterXObject **new_value) +{ + return TRUE; +} + +static gboolean +_filterx_dict_unset_key(FilterXDict *s, FilterXObject *key) +{ + return TRUE; +} + +static guint64 +_filterx_dict_size(FilterXDict *s) +{ + return 0; +} + +static gboolean +_filterx_dict_foreach(FilterXDict *s, FilterXDictIterFunc func, gpointer user_data) +{ + return TRUE; +} + +static FilterXObject * +_filterx_dict_factory(FilterXObject *self) +{ + return filterx_dict_new(); +} + +static void +_filterx_dict_free(FilterXObject *s) +{ + filterx_object_free_method(s); +} + +FilterXObject * +filterx_dict_new(void) +{ + FilterXDictObj *self = g_new0(FilterXDictObj, 1); + filterx_dict_init_instance(&self->super, &FILTERX_TYPE_NAME(dictobj)); + + self->super.get_subscript = _filterx_dict_get_subscript; + self->super.set_subscript = _filterx_dict_set_subscript; + self->super.unset_key = _filterx_dict_unset_key; + self->super.len = _filterx_dict_size; + self->super.iter = _filterx_dict_foreach; + + return &self->super.super; +} + +FilterXObject * +filterx_dict_new_from_args(FilterXExpr *s, GPtrArray *args) +{ + if (!args || args->len == 0) + return filterx_dict_new(); + + if (args->len != 1) + { + filterx_eval_push_error("Too many arguments", s, NULL); + return NULL; + } + + FilterXObject *arg = (FilterXObject *) g_ptr_array_index(args, 0); + + FilterXObject *arg_unwrapped = filterx_ref_unwrap_ro(arg); + if (filterx_object_is_type(arg_unwrapped, &FILTERX_TYPE_NAME(dictobj))) + return filterx_object_ref(arg); + + if (filterx_object_is_type(arg_unwrapped, &FILTERX_TYPE_NAME(dict))) + { + FilterXObject *self = filterx_dict_new(); + if (!filterx_dict_merge(self, arg_unwrapped)) + { + filterx_object_unref(self); + return NULL; + } + return self; + } + + const gchar *repr; + gsize repr_len; + if (filterx_object_extract_string_ref(arg, &repr, &repr_len)) + { + FilterXObject *json = filterx_json_object_new_from_repr(repr, repr_len); + + FilterXObject *self = filterx_dict_new(); + if (!filterx_dict_merge(self, json)) + { + filterx_object_unref(self); + filterx_object_unref(json); + return NULL; + } + filterx_object_unref(json); + return self; + } + + filterx_eval_push_error_info("Argument must be a dict or a string", s, + g_strdup_printf("got \"%s\" instead", arg_unwrapped->type->name), TRUE); + return NULL; +} + +FILTERX_DEFINE_TYPE(dictobj, FILTERX_TYPE_NAME(dict), + .is_mutable = TRUE, + .truthy = _filterx_dict_truthy, + .free_fn = _filterx_dict_free, + .marshal = _filterx_dict_marshal, + .repr = _filterx_dict_repr, + .clone = _filterx_dict_clone, + .dict_factory = _filterx_dict_factory, + ); diff --git a/lib/filterx/object-dict.h b/lib/filterx/object-dict.h new file mode 100644 index 000000000..1347745bf --- /dev/null +++ b/lib/filterx/object-dict.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 Axoflow + * Copyright (c) 2024 László Várady + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#ifndef FILTERX_OBJECT_DICT_H +#define FILTERX_OBJECT_DICT_H + +#include "filterx/filterx-object.h" + +typedef struct _FilterXDictObj FilterXDictObj; + +FILTERX_DECLARE_TYPE(dictobj); + +FilterXObject *filterx_dict_new(void); +FilterXObject *filterx_dict_new_from_args(FilterXExpr *s, GPtrArray *args); + +#endif From fa3fcc59ac8fb9983c11d0aecf8eaf483d6f04c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20V=C3=A1rady?= Date: Mon, 18 Nov 2024 16:00:59 +0100 Subject: [PATCH 7/8] filterx: implement native FilterXDict methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: László Várady --- lib/filterx/object-dict.c | 239 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 232 insertions(+), 7 deletions(-) diff --git a/lib/filterx/object-dict.c b/lib/filterx/object-dict.c index c2d7640bf..959fe1433 100644 --- a/lib/filterx/object-dict.c +++ b/lib/filterx/object-dict.c @@ -26,6 +26,8 @@ #include "filterx/object-dict-interface.h" #include "filterx/object-extractor.h" #include "filterx/object-json.h" +#include "filterx/object-string.h" +#include "filterx/object-message-value.h" #include "filterx/filterx-object.h" #include "filterx/filterx-object-istype.h" #include "filterx/filterx-ref.h" @@ -81,59 +83,211 @@ struct _FilterXDictObj guint8 value_cache_len; }; +static inline gboolean +_filterx_dict_value_is_ref(FilterXDictValue *value) +{ + /* safe because of __null_pad */ + return filterx_object_is_type((FilterXObject *) value, &FILTERX_TYPE_NAME(ref)); +} + static gboolean _filterx_dict_truthy(FilterXObject *s) { return TRUE; } +static inline gboolean +_filterx_dict_append_json_repr(FilterXDictObj *self, GString *repr) +{ + FilterXObject *json = filterx_json_object_new_empty(); + if (!filterx_dict_merge(json, &self->super.super)) + { + filterx_object_unref(json); + return FALSE; + } + + g_string_append(repr, filterx_json_object_to_json_literal(json)); + filterx_object_unref(json); + return TRUE; +} + static gboolean _filterx_dict_marshal(FilterXObject *s, GString *repr, LogMessageValueType *t) { - return TRUE; + FilterXDictObj *self = (FilterXDictObj *) s; + + *t = LM_VT_JSON; + return _filterx_dict_append_json_repr(self, repr); } static gboolean _filterx_dict_repr(FilterXObject *s, GString *repr) { - return TRUE; + FilterXDictObj *self = (FilterXDictObj *) s; + return _filterx_dict_append_json_repr(self, repr); +} + +static gboolean +_deep_copy_item(FilterXObject *key, FilterXObject *value, gpointer user_data) +{ + FilterXObject *clone = (FilterXObject *) user_data; + + key = filterx_object_clone(key); + FilterXObject *new_value = filterx_object_clone(value); + + gboolean ok = filterx_object_set_subscript(clone, key, &new_value); + + filterx_object_unref(new_value); + filterx_object_unref(key); + + return ok; } static FilterXObject * _filterx_dict_clone(FilterXObject *s) { - return NULL; + FilterXDictObj *self = (FilterXDictObj *) s; + + FilterXObject *clone = filterx_dict_new(); + filterx_dict_iter(&self->super.super, _deep_copy_item, clone); + return clone; +} + +static inline gboolean +_is_string(FilterXObject *o) +{ + return filterx_object_is_type(o, &FILTERX_TYPE_NAME(string)) + || (filterx_object_is_type(o, &FILTERX_TYPE_NAME(message_value)) + && filterx_message_value_get_type(o) == LM_VT_STRING); } static FilterXObject * _filterx_dict_get_subscript(FilterXDict *s, FilterXObject *key) { - return NULL; + FilterXDictObj *self = (FilterXDictObj *) s; + + if (!_is_string(key)) + return NULL; + + FilterXDictKey k = { .key_obj = key }; + FilterXDictValue *value = iord_map_lookup(&self->map, &k); + + if (!value) + return NULL; + + FilterXObject *v = _filterx_dict_value_is_ref(value) ? &value->ref.super : value->immutable.val; + return filterx_object_ref(v); +} + +static inline FilterXDictKey * +_key_cache_or_alloc(FilterXDictObj *self, FilterXObject *key) +{ + FilterXDictKey *k; + if (self->key_cache_len < CACHE_SIZE) + { + k = &self->key_cache[self->key_cache_len++]; + k->in_cache = TRUE; + } + else + k = g_new0(FilterXDictKey, 1); + + k->key_obj = filterx_object_ref(key); + + return k; +} + +static inline FilterXDictValue * +_value_cache_or_alloc(FilterXDictObj *self, FilterXObject *value) +{ + if (filterx_object_is_type(value, &FILTERX_TYPE_NAME(ref))) + return (FilterXDictValue *) filterx_object_ref(value); + + FilterXDictValue *v; + if (self->value_cache_len < CACHE_SIZE) + { + v = &self->value_cache[self->value_cache_len++]; + v->immutable.in_cache = TRUE; + } + else + v = g_new0(FilterXDictValue, 1); + + v->immutable.val = filterx_object_ref(value); + + return v; } static gboolean _filterx_dict_set_subscript(FilterXDict *s, FilterXObject *key, FilterXObject **new_value) { + FilterXDictObj *self = (FilterXDictObj *) s; + + if (!_is_string(key)) + return FALSE; + + FilterXDictKey *k = _key_cache_or_alloc(self, key); + FilterXDictValue *v = _value_cache_or_alloc(self, *new_value); + + // map other dicts/arrays to dictobj + // filterx_object_set_modified_in_place(&self->super.super, TRUE); + // filterx_object_set_modified_in_place(root_container, TRUE); + + iord_map_insert(&self->map, k, v); return TRUE; } static gboolean _filterx_dict_unset_key(FilterXDict *s, FilterXObject *key) { - return TRUE; + FilterXDictObj *self = (FilterXDictObj *) s; + + if (!_is_string(key)) + return FALSE; + + FilterXDictKey k = { .key_obj = key }; + return iord_map_remove(&self->map, &k); + + // filterx_object_set_modified_in_place(&self->super.super, TRUE); + // filterx_object_set_modified_in_place(root_container, TRUE); } static guint64 _filterx_dict_size(FilterXDict *s) { - return 0; + FilterXDictObj *self = (FilterXDictObj *) s; + + return iord_map_size(&self->map); +} + +static gboolean +_filterx_dict_foreach_inner(gpointer key, gpointer value, gpointer c) +{ + gpointer *args = (gpointer *) c; + FilterXDictIterFunc func = args[0]; + gpointer user_data = args[1]; + + FilterXDictKey *k = key; + FilterXDictValue *v = value; + + return func(k->key_obj, _filterx_dict_value_is_ref(v) ? &v->ref.super : v->immutable.val, user_data); } static gboolean _filterx_dict_foreach(FilterXDict *s, FilterXDictIterFunc func, gpointer user_data) { - return TRUE; + FilterXDictObj *self = (FilterXDictObj *) s; + + gpointer args[] = { func, user_data }; + return iord_map_foreach(&self->map, _filterx_dict_foreach_inner, args); } +/* + +static FilterXObject * +_list_factory(FilterXObject *self) +{ + return filterx_dict_array_new_empty(); +} + +*/ static FilterXObject * _filterx_dict_factory(FilterXObject *self) @@ -141,12 +295,78 @@ _filterx_dict_factory(FilterXObject *self) return filterx_dict_new(); } + static void _filterx_dict_free(FilterXObject *s) { + FilterXDictObj *self = (FilterXDictObj *) s; + + iord_map_destroy(&self->map); + + // filterx_weakref_clear(&self->root_container); + filterx_object_free_method(s); } +#define FILTERX_DICT_KEY_STR(key) \ + ({ \ + const gchar *__key_str; \ + gsize __key_str_len; \ + g_assert(filterx_object_extract_string_ref(key->key_obj, &__key_str, &__key_str_len)); \ + APPEND_ZERO(__key_str, __key_str, __key_str_len); \ + __key_str; \ + }) + +static guint +_key_hash(gconstpointer k) +{ + FilterXDictKey *key = (FilterXDictKey *) k; + + const gchar *key_str = FILTERX_DICT_KEY_STR(key); + + return g_str_hash(key_str); +} + +static gboolean +_key_equal(gconstpointer a, gconstpointer b) +{ + FilterXDictKey *key_a = (FilterXDictKey *) a; + FilterXDictKey *key_b = (FilterXDictKey *) b; + + const gchar *key_str_a = FILTERX_DICT_KEY_STR(key_a); + const gchar *key_str_b = FILTERX_DICT_KEY_STR(key_b); + + return g_str_equal(key_str_a, key_str_b); +} + +static void +_key_destroy(gpointer k) +{ + FilterXDictKey *key = (FilterXDictKey *) k; + + filterx_object_unref(key->key_obj); + + if (!key->in_cache) + g_free(key); +} + +static void +_value_destroy(gpointer v) +{ + FilterXDictValue *value = (FilterXDictValue *) v; + + if (_filterx_dict_value_is_ref(value)) + { + filterx_object_unref(&value->ref.super); + return; + } + + filterx_object_unref(value->immutable.val); + + if (!value->immutable.in_cache) + g_free(value); +} + FilterXObject * filterx_dict_new(void) { @@ -159,6 +379,11 @@ filterx_dict_new(void) self->super.len = _filterx_dict_size; self->super.iter = _filterx_dict_foreach; + //filterx_weakref_set(&self->root_container, root); + //filterx_object_unref(root); + iord_map_init_full(&self->map, _key_hash, _key_equal, _key_destroy, offsetof(FilterXDictKey, n), + _value_destroy, offsetof(FilterXDictValue, immutable.n)); + return &self->super.super; } From 83c71601749bdc0307459952459816c16626e226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20V=C3=A1rady?= Date: Tue, 19 Nov 2024 19:00:22 +0100 Subject: [PATCH 8/8] glib-compat: add g_hash_table_steal_extended() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: László Várady --- lib/compat/glib.c | 11 +++++++++++ lib/compat/glib.h | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/lib/compat/glib.c b/lib/compat/glib.c index d701e93ea..54676d741 100644 --- a/lib/compat/glib.c +++ b/lib/compat/glib.c @@ -297,3 +297,14 @@ g_utf8_get_char_validated_fixed(const gchar *p, gssize max_len) return g_utf8_get_char_validated(p, max_len); } #endif + + +#if !GLIB_CHECK_VERSION(2, 58, 0) +gboolean +slng_g_hash_table_steal_extended(GHashTable *hash_table, gconstpointer lookup_key, + gpointer *stolen_key, gpointer *stolen_value) +{ + g_hash_table_lookup_extended(hash_table, lookup_key, stolen_key, stolen_value); + return g_hash_table_steal(hash_table, lookup_key); +} +#endif diff --git a/lib/compat/glib.h b/lib/compat/glib.h index 18227fb66..eee890e82 100644 --- a/lib/compat/glib.h +++ b/lib/compat/glib.h @@ -68,4 +68,10 @@ gunichar g_utf8_get_char_validated_fixed (const gchar *p, gssize max_len); #define g_pattern_spec_match g_pattern_match #endif +#if !GLIB_CHECK_VERSION(2, 58, 0) +#define g_hash_table_steal_extended slng_g_hash_table_steal_extended +gboolean slng_g_hash_table_steal_extended(GHashTable *hash_table, gconstpointer lookup_key, + gpointer *stolen_key, gpointer *stolen_value); +#endif + #endif