From a5205458648533bf8c007cec290565c6178a8f10 Mon Sep 17 00:00:00 2001 From: Gauthier Malfilatre Date: Sun, 28 Jun 2026 23:10:06 +0200 Subject: [PATCH 01/14] Enhance README with multi-protocol support details Added details about multi-protocol support for TCP and UDP. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ceee5d4..b4044b5 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,8 @@ Key Features: - **O(1) Binding**: Dual-pointer (user_data) system for lookup-free interaction between the network engine and application entities. +- **Multi-protocol support**: Currently, kronknet supports both TCP and UDP. In TCP mode, connections are stored in a dynamic array, whereas in UDP mode, they are kept in a hash table, offering O(1) lookup complexity in most cases. +

(back to top)

From dc111affff250803340282e9adcd97340f6093fe Mon Sep 17 00:00:00 2001 From: GauthierMalfilatre Date: Fri, 3 Jul 2026 13:02:03 +0200 Subject: [PATCH 02/14] [UP] Update CMakeLists.txt to include heracles (for tests) --- CMakeLists.txt | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 697876f..b7fd2b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,50 +5,52 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) message(FATAL_ERROR "Les builds dans le dossier source sont interdits. Créez un dossier de build (ex: 'build/').") endif() +include(FetchContent) + +FetchContent_Declare( + heracles + GIT_REPOSITORY https://github.com/GauthierMalfilatre/Heracles + GIT_TAG main +) +FetchContent_MakeAvailable(heracles) + file(GLOB_RECURSE KRONKNET_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c" ) -set(CMAKE_C_VISIBILITY_PRESET hidden) -set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) - add_library(kronknet-obj OBJECT ${KRONKNET_SOURCES}) +set_target_properties(kronknet-obj PROPERTIES + C_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON +) + target_include_directories(kronknet-obj PUBLIC $ $ ) +target_link_libraries(kronknet-obj PRIVATE heracles) + target_compile_options(kronknet-obj PRIVATE -Wall -Wextra -O3 ) -set_target_properties(kronknet-obj PROPERTIES - POSITION_INDEPENDENT_CODE ON -) - add_library(kronknet-static STATIC $) target_link_libraries(kronknet-static PUBLIC kronknet-obj) add_library(kronknet-shared SHARED $) target_link_libraries(kronknet-shared PUBLIC kronknet-obj) - target_compile_definitions(kronknet-shared PRIVATE KRONKNET_BUILD_DLL) add_library(kronknet ALIAS kronknet-static) -set_target_properties(kronknet-static PROPERTIES - OUTPUT_NAME kronknet -) - -set_target_properties(kronknet-shared PROPERTIES - OUTPUT_NAME kronknet -) - -# NOTE: Installation +set_target_properties(kronknet-static PROPERTIES OUTPUT_NAME kronknet) +set_target_properties(kronknet-shared PROPERTIES OUTPUT_NAME kronknet) include(GNUInstallDirs) From e7c2b71c5486c784a87edcb440204f117fda3828 Mon Sep 17 00:00:00 2001 From: GauthierMalfilatre Date: Fri, 3 Jul 2026 13:28:03 +0200 Subject: [PATCH 03/14] [ADD] Add tests --- CMakeLists.txt | 3 +-- tests/CMakeLists.txt | 10 +++++++++ tests/utils/tests_knmap.c | 44 ++++++++++++++++++++------------------- 3 files changed, 34 insertions(+), 23 deletions(-) create mode 100644 tests/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index b7fd2b4..3855c10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/GauthierMalfilatre/Heracles GIT_TAG main ) + FetchContent_MakeAvailable(heracles) file(GLOB_RECURSE KRONKNET_SOURCES @@ -32,8 +33,6 @@ target_include_directories(kronknet-obj $ ) -target_link_libraries(kronknet-obj PRIVATE heracles) - target_compile_options(kronknet-obj PRIVATE -Wall -Wextra diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..ad9cab9 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.20) +project(kronknet_tests LANGUAGES C) + +file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/utils/*.c) + +add_executable(kronknet_tests ${SOURCES}) + +target_include_directories(kronknet_tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/) + +target_link_libraries(kronknet_tests PRIVATE heracles kronknet) diff --git a/tests/utils/tests_knmap.c b/tests/utils/tests_knmap.c index 56e4b55..c097ee3 100644 --- a/tests/utils/tests_knmap.c +++ b/tests/utils/tests_knmap.c @@ -1,6 +1,8 @@ -#include +#include +#include +#include #include -#include "kronknet/utils/hashmap/hashmap.h" +#include static uint64_t dummy_collision_hash(uint64_t key) { (void)key; @@ -25,17 +27,17 @@ static void spy_deleter(void *data) { Test(hashmap, insert_and_search_nominal) { knMap *map = knMap_create(stable_hash, 8); - cr_assert_not_null(map); - + AssertExists(map, "Map is null"); + char *val1 = strdup("Client_UDP_1"); char *val2 = strdup("Client_UDP_2"); - cr_assert_eq(knMap_insert(map, 111122223333ULL, val1, free), 0); - cr_assert_eq(knMap_insert(map, 444455556666ULL, val2, free), 0); + AssertEq(knMap_insert(map, 111122223333ULL, val1, free), 0, "not equal"); + AssertEq(knMap_insert(map, 444455556666ULL, val2, free), 0, "not equal"); - cr_assert_eq(knMap_search(map, 111122223333ULL), val1); - cr_assert_eq(knMap_search(map, 444455556666ULL), val2); - cr_assert_null(knMap_search(map, 999999999999ULL)); + AssertEq(knMap_search(map, 111122223333ULL), val1, "not equal"); + AssertEq(knMap_search(map, 444455556666ULL), val2, "not equal"); + Assert(knMap_search(map, 999999999999ULL) == NULL, "not equal"); knMap_destroy(map); } @@ -43,18 +45,18 @@ Test(hashmap, insert_and_search_nominal) Test(hashmap, overwrite_and_deleter) { knMap *map = knMap_create(stable_hash, 8); - cr_assert_not_null(map); + AssertExists(map, "not equal"); g_delete_calls = 0; char *val1 = strdup("Premier_Passage"); char *val2 = strdup("Deuxieme_Passage"); knMap_insert(map, 42ULL, val1, spy_deleter); - cr_assert_eq(g_delete_calls, 0); + AssertEq(g_delete_calls, 0, "not equal"); knMap_insert(map, 42ULL, val2, spy_deleter); - cr_assert_eq(g_delete_calls, 1); - cr_assert_eq(knMap_search(map, 42ULL), val2); + AssertEq(g_delete_calls, 1, "not equal"); + AssertEq(knMap_search(map, 42ULL), val2, "not equal"); knMap_destroy(map); } @@ -62,7 +64,7 @@ Test(hashmap, overwrite_and_deleter) Test(hashmap, hash_collision_handling) { knMap *map = knMap_create(dummy_collision_hash, 8); - cr_assert_not_null(map); + AssertExists(map, "Map is null"); char *val1 = strdup("Data_1"); char *val2 = strdup("Data_2"); @@ -70,8 +72,8 @@ Test(hashmap, hash_collision_handling) knMap_insert(map, 10ULL, val1, free); knMap_insert(map, 20ULL, val2, free); - cr_assert_eq(knMap_search(map, 10ULL), val1); - cr_assert_eq(knMap_search(map, 20ULL), val2); + AssertEq(knMap_search(map, 10ULL), val1, "not equal"); + AssertEq(knMap_search(map, 20ULL), val2, "not equal"); knMap_destroy(map); } @@ -79,17 +81,17 @@ Test(hashmap, hash_collision_handling) Test(hashmap, auto_rehash) { knMap *map = knMap_create(stable_hash, 4); - cr_assert_not_null(map); + AssertExists(map, "Map is null"); knMap_insert(map, 1ULL, strdup("A"), free); knMap_insert(map, 2ULL, strdup("B"), free); knMap_insert(map, 3ULL, strdup("C"), free); knMap_insert(map, 4ULL, strdup("D"), free); - cr_assert_not_null(knMap_search(map, 1ULL)); - cr_assert_not_null(knMap_search(map, 2ULL)); - cr_assert_not_null(knMap_search(map, 3ULL)); - cr_assert_not_null(knMap_search(map, 4ULL)); + AssertExists(knMap_search(map, 1ULL), "Should exists"); + AssertExists(knMap_search(map, 2ULL), "Should exists"); + AssertExists(knMap_search(map, 3ULL), "Should exists"); + AssertExists(knMap_search(map, 4ULL), "Should exists"); knMap_destroy(map); } From 4820b7e6f95c27c42f867f06f6f286e616941f55 Mon Sep 17 00:00:00 2001 From: GauthierMalfilatre Date: Fri, 3 Jul 2026 13:39:43 +0200 Subject: [PATCH 04/14] [ADD] Add more tests, and switch to Heracles framework --- src/utils/hashmap/hash.c | 2 + tests/utils/tests_knmap.c | 177 +++++++++++++++++++++++------------ tests/utils/tests_knrbuff.c | 179 ++++++++++++++++++++++++++++++++++++ 3 files changed, 300 insertions(+), 58 deletions(-) create mode 100644 tests/utils/tests_knrbuff.c diff --git a/src/utils/hashmap/hash.c b/src/utils/hashmap/hash.c index bc2f40f..c98442f 100644 --- a/src/utils/hashmap/hash.c +++ b/src/utils/hashmap/hash.c @@ -4,8 +4,10 @@ ** File description: ** Thomas Wang hash algorithm: https://gist.github.com/badboy/6267743 */ +#include "kronknet/macros/optimization.h" #include +KN_API uint64_t knMap_basicHash( uint64_t key ) diff --git a/tests/utils/tests_knmap.c b/tests/utils/tests_knmap.c index c097ee3..574592c 100644 --- a/tests/utils/tests_knmap.c +++ b/tests/utils/tests_knmap.c @@ -1,97 +1,158 @@ +#include +#include "kronknet/utils/hashmap/hashmap.h" #include -#include #include #include -#include -static uint64_t dummy_collision_hash(uint64_t key) { +static int g_deleter_call_count = 0; + +static void mock_deleter(void *data) +{ + (void)data; + g_deleter_call_count++; +} + +static void mock_foreach_counter(uint64_t key, void *value, void *param) +{ (void)key; - return 42; + (void)value; + int *count = (int *)param; + (*count)++; } -static uint64_t stable_hash(uint64_t key) { - key ^= key >> 30; - key *= 0xbf58476d1ce4e5b9ULL; - key ^= key >> 27; - key *= 0x94d049bb133111ebULL; - key ^= key >> 31; - return key; +Test(hashmap, basic_hash_deterministic) +{ + uint64_t hash1 = knMap_basicHash(12345); + uint64_t hash2 = knMap_basicHash(12345); + uint64_t hash3 = knMap_basicHash(54321); + + AssertEq(hash1, hash2, "Le hash d'une même clé doit toujours être identique"); + Assert(hash1 != hash3, "Des clés différentes doivent (idéalement) produire des hash différents"); } -static int g_delete_calls = 0; -static void spy_deleter(void *data) { - g_delete_calls++; - free(data); +Test(hashmap, create_destroy_empty) +{ + knMap *map = knMap_create(knMap_basicHash, 32); + AssertExists(map, "La création de la map ne doit pas renvoyer NULL"); + knMap_destroy(map); } -Test(hashmap, insert_and_search_nominal) +Test(hashmap, insert_and_search_success) { - knMap *map = knMap_create(stable_hash, 8); - AssertExists(map, "Map is null"); + knMap *map = knMap_create(knMap_basicHash, 16); + uint64_t key = 42; + char *value = "kronknet_data"; + + int res = knMap_insert(map, key, value, NULL); + AssertEq(res, 0, "L'insertion doit retourner 0 en cas de succès"); - char *val1 = strdup("Client_UDP_1"); - char *val2 = strdup("Client_UDP_2"); + void *found = knMap_search(map, key); + AssertExists(found, "La donnée insérée doit être trouvée"); + AssertEq((char*)found, value, "Le pointeur retourné doit correspondre à la valeur insérée"); - AssertEq(knMap_insert(map, 111122223333ULL, val1, free), 0, "not equal"); - AssertEq(knMap_insert(map, 444455556666ULL, val2, free), 0, "not equal"); + knMap_destroy(map); +} - AssertEq(knMap_search(map, 111122223333ULL), val1, "not equal"); - AssertEq(knMap_search(map, 444455556666ULL), val2, "not equal"); - Assert(knMap_search(map, 999999999999ULL) == NULL, "not equal"); +Test(hashmap, search_not_found) +{ + knMap *map = knMap_create(knMap_basicHash, 16); + + knMap_insert(map, 10, "A", NULL); + + void *found = knMap_search(map, 999); + Assert(found == NULL, "La recherche d'une clé inexistante doit renvoyer NULL"); knMap_destroy(map); } -Test(hashmap, overwrite_and_deleter) +Test(hashmap, overwrite_calls_deleter) { - knMap *map = knMap_create(stable_hash, 8); - AssertExists(map, "not equal"); - g_delete_calls = 0; + knMap *map = knMap_create(knMap_basicHash, 16); + g_deleter_call_count = 0; + + uint64_t key = 100; + char *val1 = "old_data"; + char *val2 = "new_data"; - char *val1 = strdup("Premier_Passage"); - char *val2 = strdup("Deuxieme_Passage"); + knMap_insert(map, key, val1, mock_deleter); + knMap_insert(map, key, val2, mock_deleter); - knMap_insert(map, 42ULL, val1, spy_deleter); - AssertEq(g_delete_calls, 0, "not equal"); + AssertEq(g_deleter_call_count, 1, "Le deleter aurait dû être appelé 1 fois lors de l'écrasement"); - knMap_insert(map, 42ULL, val2, spy_deleter); - AssertEq(g_delete_calls, 1, "not equal"); - AssertEq(knMap_search(map, 42ULL), val2, "not equal"); + void *found = knMap_search(map, key); + AssertEq((char*)found, val2, "La valeur doit être mise à jour après écrasement"); knMap_destroy(map); + AssertEq(g_deleter_call_count, 2, "Le deleter aurait dû être appelé à la destruction de la map"); } -Test(hashmap, hash_collision_handling) +Test(hashmap, explicit_delete_bypasse_deleter) { - knMap *map = knMap_create(dummy_collision_hash, 8); - AssertExists(map, "Map is null"); + knMap *map = knMap_create(knMap_basicHash, 16); + g_deleter_call_count = 0; + + uint64_t key = 50; + char *val = "temp_data"; + + knMap_insert(map, key, val, mock_deleter); + + int res = knMap_delete(map, key); + AssertEq(res, 0, "La suppression doit renvoyer 0"); + + AssertEq(g_deleter_call_count, 0, "knMap_delete NE DOIT PAS appeler le deleter interne"); + + void *found = knMap_search(map, key); + Assert(found == NULL, "L'élément supprimé ne doit plus être trouvable"); + + knMap_destroy(map); + AssertEq(g_deleter_call_count, 0, "L'élément étant déjà supprimé, la destruction ne doit pas trigger de deleter"); +} - char *val1 = strdup("Data_1"); - char *val2 = strdup("Data_2"); +Test(hashmap, delete_non_existent) +{ + knMap *map = knMap_create(knMap_basicHash, 16); + + int res = knMap_delete(map, 404); + AssertEq(res, -1, "La suppression d'un élément inexistant doit renvoyer -1"); + + knMap_destroy(map); +} - knMap_insert(map, 10ULL, val1, free); - knMap_insert(map, 20ULL, val2, free); +Test(hashmap, foreach_traversal) +{ + knMap *map = knMap_create(knMap_basicHash, 16); - AssertEq(knMap_search(map, 10ULL), val1, "not equal"); - AssertEq(knMap_search(map, 20ULL), val2, "not equal"); + knMap_insert(map, 1, "A", NULL); + knMap_insert(map, 2, "B", NULL); + knMap_insert(map, 3, "C", NULL); + + int count = 0; + knMap_foreach(map, mock_foreach_counter, &count); + + AssertEq(count, 3, "knMap_foreach doit itérer exactement 3 fois"); knMap_destroy(map); } -Test(hashmap, auto_rehash) +Test(hashmap, stress_and_collisions) { - knMap *map = knMap_create(stable_hash, 4); - AssertExists(map, "Map is null"); - - knMap_insert(map, 1ULL, strdup("A"), free); - knMap_insert(map, 2ULL, strdup("B"), free); - knMap_insert(map, 3ULL, strdup("C"), free); - knMap_insert(map, 4ULL, strdup("D"), free); - - AssertExists(knMap_search(map, 1ULL), "Should exists"); - AssertExists(knMap_search(map, 2ULL), "Should exists"); - AssertExists(knMap_search(map, 3ULL), "Should exists"); - AssertExists(knMap_search(map, 4ULL), "Should exists"); + knMap *map = knMap_create(knMap_basicHash, 8); + int iterations = 1000; + + for (uint64_t i = 0; i < (uint64_t)iterations; i++) { + knMap_insert(map, i, (void*)(uintptr_t)i, NULL); + } + + int success = 1; + for (uint64_t i = 0; i < (uint64_t)iterations; i++) { + void *found = knMap_search(map, i); + if (found != (void*)(uintptr_t)i) { + success = 0; + break; + } + } + + AssertEq(success, 1, "Tous les 1000 éléments doivent être retrouvés malgré les collisions"); knMap_destroy(map); } diff --git a/tests/utils/tests_knrbuff.c b/tests/utils/tests_knrbuff.c new file mode 100644 index 0000000..aa2dc8c --- /dev/null +++ b/tests/utils/tests_knrbuff.c @@ -0,0 +1,179 @@ +#include +#include "kronknet/macros/types.h" +#include +#include +#include +#include + +Test(ring_buffer, invalid_size) +{ + knRBuff *buff = knRBuff_create(255); + Assert(buff == NULL, "Buffer creation should fail for non-power-of-2 size"); +} + +Test(ring_buffer, valid_size) +{ + knRBuff *buff = knRBuff_create(256); + AssertExists(buff, "Buffer should be successfully created"); + knRBuff_destroy(buff); +} + +Test(ring_buffer, state_empty) +{ + knRBuff *buff = knRBuff_create(256); + + Assert(knRBuff_isEmpty(buff) == knTrue, "Buffer should be empty upon creation"); + AssertEq(knRBuff_usage(buff), (size_t)0, "Usage should be 0"); + // DEBUG: One byte for full or not + AssertEq(knRBuff_remaining(buff) + 1, (size_t)256, "Remaining size should be equal to total size"); + + knRBuff_destroy(buff); +} + +Test(ring_buffer, valid_push_pop) +{ + knRBuff *buff = knRBuff_create(256); + const char *src = "hello kronknet"; + char dest[32] = {0}; + size_t len = strlen(src); + + ssize_t pushed = knRBuff_push(buff, (const uint8_t *)src, len); + AssertEq(pushed, (ssize_t)len, "Should successfully push all bytes"); + Assert(knRBuff_isEmpty(buff) == knFalse, "Buffer should not be empty after push"); + + ssize_t popped = knRBuff_pop(buff, (uint8_t *)dest, len); + AssertEq(popped, (ssize_t)len, "Should successfully pop all bytes"); + AssertEq(memcmp(src, dest, len), 0, "Popped data should match pushed data"); + Assert(knRBuff_isEmpty(buff) == knTrue, "Buffer should be empty after full pop"); + + knRBuff_destroy(buff); +} + +Test(ring_buffer, push_overflow) +{ + knRBuff *buff = knRBuff_create(16); + uint8_t data[20] = {0}; + + ssize_t pushed = knRBuff_push(buff, data, 20); + AssertEq(pushed, (ssize_t)-1, "Push should fail and return -1 when size > remaining"); + + knRBuff_destroy(buff); +} + +Test(ring_buffer, pop_underflow) +{ + knRBuff *buff = knRBuff_create(16); + uint8_t src[4] = {1, 2, 3, 4}; + uint8_t dest[8] = {0}; + + knRBuff_push(buff, src, 4); + + ssize_t popped = knRBuff_pop(buff, dest, 6); + AssertEq(popped, (ssize_t)-1, "Pop should fail and return -1 when requesting more bytes than usage"); + + knRBuff_destroy(buff); +} + +Test(ring_buffer, invalid_args_push_pop) +{ + knRBuff *buff = knRBuff_create(16); + uint8_t data[4] = {1, 2, 3, 4}; + + AssertEq(knRBuff_push(buff, NULL, 4), (ssize_t)-1, "Pushing NULL src should fail"); + AssertEq(knRBuff_pop(buff, NULL, 4), (ssize_t)-1, "Popping to NULL dest should fail"); + + knRBuff_destroy(buff); +} + +Test(ring_buffer, valid_peek) +{ + knRBuff *buff = knRBuff_create(16); + const uint8_t src[4] = {42, 43, 44, 45}; + uint8_t dest[4] = {0}; + + knRBuff_push(buff, src, 4); + + ssize_t peeked1 = knRBuff_peek(buff, dest, 4); + AssertEq(peeked1, (ssize_t)4, "Peek should succeed"); + AssertEq(memcmp(src, dest, 4), 0, "Peeked data should match"); + AssertEq(knRBuff_usage(buff), (size_t)4, "Usage should not decrease after a peek"); + + memset(dest, 0, 4); + ssize_t peeked2 = knRBuff_peek(buff, dest, 4); + AssertEq(peeked2, (ssize_t)4, "Second peek should also succeed"); + AssertEq(memcmp(src, dest, 4), 0, "Peeked data should still match"); + + knRBuff_destroy(buff); +} + +Test(ring_buffer, peek_underflow_and_invalid) +{ + knRBuff *buff = knRBuff_create(16); + uint8_t src[2] = {1, 2}; + uint8_t dest[4] = {0}; + + knRBuff_push(buff, src, 2); + + AssertEq(knRBuff_peek(buff, dest, 4), (ssize_t)-1, "Peek should fail if requesting more bytes than available"); + AssertEq(knRBuff_peek(buff, NULL, 2), (ssize_t)-1, "Peek to NULL destination should fail"); + + knRBuff_destroy(buff); +} + +Test(ring_buffer, find_sequence_success) +{ + knRBuff *buff = knRBuff_create(32); + const uint8_t payload[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + knRBuff_push(buff, payload, sizeof(payload)); + + const uint8_t target1[] = {0xCC, 0xDD}; + ssize_t idx1 = knRBuff_find(buff, target1, sizeof(target1)); + AssertEq(idx1, (ssize_t)2, "Sequence {0xCC, 0xDD} should be found at index 2"); + + const uint8_t target2[] = {0xAA}; + ssize_t idx2 = knRBuff_find(buff, target2, sizeof(target2)); + AssertEq(idx2, (ssize_t)0, "Sequence {0xAA} should be found at index 0"); + + knRBuff_destroy(buff); +} + +Test(ring_buffer, find_sequence_not_found) +{ + knRBuff *buff = knRBuff_create(32); + const uint8_t payload[] = {1, 2, 3, 4, 5}; + knRBuff_push(buff, payload, sizeof(payload)); + + const uint8_t target[] = {3, 5}; + ssize_t idx = knRBuff_find(buff, target, sizeof(target)); + AssertEq(idx, (ssize_t)-1, "Should return -1 when sequence is not found"); + + const uint8_t long_target[] = {1, 2, 3, 4, 5, 6, 7}; + AssertEq(knRBuff_find(buff, long_target, sizeof(long_target)), (ssize_t)-1, "Should return -1 if target is longer than usage"); + + knRBuff_destroy(buff); +} + +Test(ring_buffer, circular_wraparound) +{ + knRBuff *buff = knRBuff_create(16); + uint8_t dummy[12] = {0}; + uint8_t pattern[8] = {1, 2, 3, 4, 5, 6, 7, 8}; + uint8_t output[8] = {0}; + + knRBuff_push(buff, dummy, 12); + knRBuff_pop(buff, dummy, 12); + + ssize_t pushed = knRBuff_push(buff, pattern, 8); + AssertEq(pushed, (ssize_t)8, "Should successfully push across the buffer boundary"); + AssertEq(knRBuff_usage(buff), (size_t)8, "Usage should be 8"); + + const uint8_t sub_pattern[] = {4, 5, 6}; + ssize_t idx = knRBuff_find(buff, sub_pattern, sizeof(sub_pattern)); + Assert(idx != -1, "Should find the sub-pattern even if wrapped"); + + ssize_t popped = knRBuff_pop(buff, output, 8); + AssertEq(popped, (ssize_t)8, "Should successfully pop across the buffer boundary"); + AssertEq(memcmp(pattern, output, 8), 0, "Data integrity maintained after wraparound"); + + knRBuff_destroy(buff); +} From fa3d2a79d9cd0a076fc5f820fc7210a76bdeec58 Mon Sep 17 00:00:00 2001 From: Eren Turkoglu Date: Tue, 7 Jul 2026 08:44:18 +0200 Subject: [PATCH 05/14] Add PR template --- .github/pull_request_template.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..e69683c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,17 @@ +## Description of the change. + +## Related Issue + +Closes # ? + +## Type of Change + +- [ ] Bug fix +- [ ] Refactor +- [ ] Performance improvement +- [ ] New feature +- [ ] Tests +- [ ] CI/CD +- [ ] Documentation + +## Tests From 48d7c0a709c81712e3316fef988723130f08f7d1 Mon Sep 17 00:00:00 2001 From: Eren Turkoglu Date: Tue, 7 Jul 2026 08:57:19 +0200 Subject: [PATCH 06/14] Add bug report template --- .github/ISSUE_TEMPLATE/bug_report.md | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..6514ffd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report of the bug you've encountered +title: "[BUG] - ISSUE_TITLE" +labels: bug +assignees: kronkcorp + +--- + +## Description + +A clear and concise description of what the bug is. + +## Steps to reproduce + +Steps to reproduce the bug you encountered + +## Expected behaviour + +A clear and concise description of what you expected to happen. + +## Environment + +- OS: [e.g. GNU/Linux Debian 10] +- Architecture [Arm, x86_64, ...] +- C version +- library version +- Protocol used +- Remote server version and name + +## Additional information + +Add any other context about the problem here. From eb80efd80cb66842d82a4d65041198e1527dca8c Mon Sep 17 00:00:00 2001 From: Eren Turkoglu Date: Tue, 7 Jul 2026 08:58:45 +0200 Subject: [PATCH 07/14] Add feature request template --- .github/ISSUE_TEMPLATE/feature_request.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..b2b3079 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: Feature request +about: Suggest an idea to improve kronkcorp +title: "[Feature Request] - FEATURE_TITLE" +labels: "new feature" +assignees: kronkcorp + +--- + +## Description + +Put here a brief introduction to your suggestion. + +### Changes + +The following changes to the application are expected + +- ... + +## Implementation + +Provide any kind of suggestion you propose on how to implement the feature. +If you have none, delete this section. From 34fcdf00ce68544077d5be8e8785b575f29eaf1b Mon Sep 17 00:00:00 2001 From: Eren Turkoglu Date: Tue, 7 Jul 2026 08:58:52 +0200 Subject: [PATCH 08/14] Add question template --- .github/ISSUE_TEMPLATE/question.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/question.md diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..4863f66 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,8 @@ +--- +name: Question +about: Ask what you want about the project +title: "[QUESTION] - TITLE" +labels: question +assignees: kronkcorp + +--- From 67b56c6f05232797ae859f1db881356785905a42 Mon Sep 17 00:00:00 2001 From: GauthierMalfilatre Date: Wed, 8 Jul 2026 20:22:19 +0200 Subject: [PATCH 09/14] [UP] Update tests. --- tests/CMakeLists.txt | 2 +- tests/utils/tests_knmap.c | 5 +++-- tests/utils/tests_knrbuff.c | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ad9cab9..91d16c1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,4 +7,4 @@ add_executable(kronknet_tests ${SOURCES}) target_include_directories(kronknet_tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/) -target_link_libraries(kronknet_tests PRIVATE heracles kronknet) +target_link_libraries(kronknet_tests PRIVATE kronklab kronknet) diff --git a/tests/utils/tests_knmap.c b/tests/utils/tests_knmap.c index 574592c..8938e1c 100644 --- a/tests/utils/tests_knmap.c +++ b/tests/utils/tests_knmap.c @@ -1,4 +1,5 @@ #include +#include #include "kronknet/utils/hashmap/hashmap.h" #include #include @@ -33,7 +34,7 @@ Test(hashmap, basic_hash_deterministic) Test(hashmap, create_destroy_empty) { knMap *map = knMap_create(knMap_basicHash, 32); - AssertExists(map, "La création de la map ne doit pas renvoyer NULL"); + AssertNotNull(map, "La création de la map ne doit pas renvoyer NULL"); knMap_destroy(map); } @@ -47,7 +48,7 @@ Test(hashmap, insert_and_search_success) AssertEq(res, 0, "L'insertion doit retourner 0 en cas de succès"); void *found = knMap_search(map, key); - AssertExists(found, "La donnée insérée doit être trouvée"); + AssertNotNull(found, "La donnée insérée doit être trouvée"); AssertEq((char*)found, value, "Le pointeur retourné doit correspondre à la valeur insérée"); knMap_destroy(map); diff --git a/tests/utils/tests_knrbuff.c b/tests/utils/tests_knrbuff.c index aa2dc8c..ddabe5e 100644 --- a/tests/utils/tests_knrbuff.c +++ b/tests/utils/tests_knrbuff.c @@ -1,4 +1,4 @@ -#include +#include #include "kronknet/macros/types.h" #include #include @@ -14,7 +14,7 @@ Test(ring_buffer, invalid_size) Test(ring_buffer, valid_size) { knRBuff *buff = knRBuff_create(256); - AssertExists(buff, "Buffer should be successfully created"); + AssertNotNull(buff, "Buffer should be successfully created"); knRBuff_destroy(buff); } From eba48cf676550c1e6076b4aa9eddbd6ae5409cc9 Mon Sep 17 00:00:00 2001 From: GauthierMalfilatre Date: Fri, 10 Jul 2026 14:53:29 +0200 Subject: [PATCH 10/14] [UP] Update heracles to kronklab in cmakelists --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3855c10..2bbf852 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,12 +8,12 @@ endif() include(FetchContent) FetchContent_Declare( - heracles - GIT_REPOSITORY https://github.com/GauthierMalfilatre/Heracles + kronklab + GIT_REPOSITORY https://github.com/kronkorp/kronklab GIT_TAG main ) -FetchContent_MakeAvailable(heracles) +FetchContent_MakeAvailable(kronklab) file(GLOB_RECURSE KRONKNET_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c" From 456c407daf09fe9adc43c9cae7443b598f735a6f Mon Sep 17 00:00:00 2001 From: GauthierMalfilatre Date: Wed, 23 Sep 2026 21:41:27 +0200 Subject: [PATCH 11/14] [FIX] Fix tests and kronklab fetching. --- CMakeLists.txt | 10 ---------- tests/CMakeLists.txt | 10 ++++++++++ tests/utils/tests_knmap.c | 1 - 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bbf852..ebd8f1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,16 +5,6 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) message(FATAL_ERROR "Les builds dans le dossier source sont interdits. Créez un dossier de build (ex: 'build/').") endif() -include(FetchContent) - -FetchContent_Declare( - kronklab - GIT_REPOSITORY https://github.com/kronkorp/kronklab - GIT_TAG main -) - -FetchContent_MakeAvailable(kronklab) - file(GLOB_RECURSE KRONKNET_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c" ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 91d16c1..f5146c7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,16 @@ cmake_minimum_required(VERSION 3.20) project(kronknet_tests LANGUAGES C) +include(FetchContent) + +FetchContent_Declare( + kronklab + GIT_REPOSITORY https://github.com/kronkorp/kronklab + GIT_TAG main +) + +FetchContent_MakeAvailable(kronklab) + file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/utils/*.c) add_executable(kronknet_tests ${SOURCES}) diff --git a/tests/utils/tests_knmap.c b/tests/utils/tests_knmap.c index 8938e1c..a359b76 100644 --- a/tests/utils/tests_knmap.c +++ b/tests/utils/tests_knmap.c @@ -1,4 +1,3 @@ -#include #include #include "kronknet/utils/hashmap/hashmap.h" #include From b5d9e5b137d502b6f40b4b241d75fc776bf26860 Mon Sep 17 00:00:00 2001 From: GauthierMalfilatre Date: Wed, 23 Sep 2026 22:09:23 +0200 Subject: [PATCH 12/14] [DEL] Remove old folders mkn/ --- include/kronknet/mkn/new.h | 26 ----- include/kronknet/mkn/object.h | 79 ---------------- include/kronknet/mkn/server.h | 52 ---------- src/mkn/new.c | 52 ---------- src/mkn/server.c | 173 ---------------------------------- 5 files changed, 382 deletions(-) delete mode 100644 include/kronknet/mkn/new.h delete mode 100644 include/kronknet/mkn/object.h delete mode 100644 include/kronknet/mkn/server.h delete mode 100644 src/mkn/new.c delete mode 100644 src/mkn/server.c diff --git a/include/kronknet/mkn/new.h b/include/kronknet/mkn/new.h deleted file mode 100644 index 7ca4af7..0000000 --- a/include/kronknet/mkn/new.h +++ /dev/null @@ -1,26 +0,0 @@ -/* -** FREE PROJECT, 2026 -** KRONKNET -** File description: -** Object construction helpers for the MKN layer. -*/ -#ifndef MKRONKNET_NEW_H - #define MKRONKNET_NEW_H - #include - #include "kronknet/mkn/object.h" - -mknObject *new(const mknClass *class, ...); -/////////////////////////////////////////////////////////////////////////////// -/** - * @brief Construct a new object instance from a va_list. - */ -/////////////////////////////////////////////////////////////////////////////// -mknObject *va_new(const mknClass *class, va_list *ap); -/////////////////////////////////////////////////////////////////////////////// -/** - * @brief Destroy an object instance. - */ -/////////////////////////////////////////////////////////////////////////////// -void delete(mknObject *ptr); - -#endif /* MKRONKNET_NEW_H */ diff --git a/include/kronknet/mkn/object.h b/include/kronknet/mkn/object.h deleted file mode 100644 index c97ac69..0000000 --- a/include/kronknet/mkn/object.h +++ /dev/null @@ -1,79 +0,0 @@ -/* -** FREE PROJECT, 2026 -** KRONKNET -** File description: -** Core object model used by the MKN layer. -*/ -#ifndef MKRONKNET_OBJECT_H - #define MKRONKNET_OBJECT_H - #include - #include - #include - #include - #include "kronknet/macros/errdef.h" - -/////////////////////////////////////////////////////////////////////////////// -/** - * @brief Opaque base type for all MKN objects. - */ -/////////////////////////////////////////////////////////////////////////////// -typedef void mknObject; -/////////////////////////////////////////////////////////////////////////////// -/** - * @brief Constructor signature used by MKN classes. - */ -/////////////////////////////////////////////////////////////////////////////// -typedef void (*ctor_t)(mknObject *this, va_list *args); -/////////////////////////////////////////////////////////////////////////////// -/** - * @brief Destructor signature used by MKN classes. - */ -/////////////////////////////////////////////////////////////////////////////// -typedef void (*dtor_t)(mknObject *this); -/////////////////////////////////////////////////////////////////////////////// -/** - * @brief String conversion signature used by MKN classes. - */ -/////////////////////////////////////////////////////////////////////////////// -typedef char *(*to_string_t)(mknObject *this); -/////////////////////////////////////////////////////////////////////////////// -/** - * @brief Binary operator signature used by MKN classes. - */ -/////////////////////////////////////////////////////////////////////////////// -typedef mknObject *(*binary_operator_t)(const mknObject *this, const mknObject *other); -/////////////////////////////////////////////////////////////////////////////// -/** - * @brief Binary comparator signature used by MKN classes. - */ -/////////////////////////////////////////////////////////////////////////////// -typedef bool (*binary_comparator_t)(const mknObject *this, const mknObject *other); - -/////////////////////////////////////////////////////////////////////////////// -typedef struct mkronknet_class_s { - - const size_t __size__; - const char* __name__; - ctor_t __ctor__; - dtor_t __dtor__; - to_string_t __str__; - binary_operator_t __add__; - binary_operator_t __sub__; - binary_operator_t __mul__; - binary_operator_t __div__; - binary_comparator_t __eq__; - binary_comparator_t __gt__; - binary_comparator_t __lt__; - -} mknClass; - -#define str(o) (((mknClass *)o)->__str__ != NULL ? ((mknClass *)o)->__str__(o) : strdup(((mknClass *)o)->__name__)) -#define addition(a, b) ((mknClass *)a)->__add__(a, b) -#define subtraction(a, b) ((mknClass *)a)->__sub__(a, b) -#define multiplication(a, b) ((mknClass *)a)->__mul__(a, b) -#define division(a, b) ((mknClass *)a)->__div__(a, b) -#define eq(a, b) ((mknClass *)a)->__eq__(a, b) -#define gt(a, b) ((mknClass *)a)->__gt__(a, b) -#define lt(a, b) ((mknClass *)a)->__lt__(a, b) - -#endif /* MKRONKNET_OBJECT_H */ diff --git a/include/kronknet/mkn/server.h b/include/kronknet/mkn/server.h deleted file mode 100644 index 0ab8a64..0000000 --- a/include/kronknet/mkn/server.h +++ /dev/null @@ -1,52 +0,0 @@ -/* -** FREE PROJECT, 2026 -** KRONKNET -** File description: -** Server object definition for the MKN layer. -*/ -#ifndef MKRONKNET_SERVER_H - #define MKRONKNET_SERVER_H - #include "kronknet/mkn/object.h" - #include - -typedef struct mknServerContainer_s mknServerContainer; - -typedef int (*mkn_run_t)(mknServerContainer *); -typedef int (*mkn_runOnce_t)(mknServerContainer *, ssize_t); -typedef int (*mkn_setCb_t)(mknServerContainer *, int (*)()); -typedef int (*mkn_getInt_t)(mknServerContainer *); -typedef int (*mkn_set_t)(mknServerContainer *, ...); - -/////////////////////////////////////////////////////////////////////////////// -/** - * @brief MKN wrapper around the server implementation. - */ -/////////////////////////////////////////////////////////////////////////////// -struct mknServerContainer_s { - - mknClass base; - mkn_run_t __run__; - mkn_runOnce_t __runOnce__; - mkn_setCb_t __onRead__; - mkn_setCb_t __onWrite__; - mkn_setCb_t __onConnect__; - mkn_setCb_t __onDisconnect__; - mkn_getInt_t __port__; - mkn_set_t __log__; - mkn_set_t __data__; - -}; - -#define run(o) (((mknServerContainer *)o)->__run__(o)) -#define runOnce(o, t) (((mknServerContainer *)o)->__runOnce__(o, t)) -#define onRead(o, c) (((mknServerContainer *)o)->__onRead__(o, c)) -#define onWrite(o, c) (((mknServerContainer *)o)->__onWrite__(o, c)) -#define onConnect(o, c) (((mknServerContainer *)o)->__onConnect__(o, c)) -#define onDisconnect(o, c) (((mknServerContainer *)o)->__onDisconnect__(o, c)) -#define port(o) (((mknServerContainer *)o)->__port__(o)) -#define setLogging(o, ...) (((mknServerContainer *)o)->__log__(o, __VA_ARGS__)) -#define setUserPtr(o, ...) (((mknServerContainer *)o)->__data__(o, __VA_ARGS__)) - -extern const mknClass *mknServer; - -#endif /* MKRONKNET_SERVER_H */ diff --git a/src/mkn/new.c b/src/mkn/new.c deleted file mode 100644 index 7b8aea2..0000000 --- a/src/mkn/new.c +++ /dev/null @@ -1,52 +0,0 @@ -/* -** FREE PROJECT, 2026 -** KRONKNET -** File description: -** New impl for MKN -*/ -#include "kronknet/mkn/object.h" -#include "kronknet/mkn/new.h" -#include "kronknet/macros/errdef.h" - -mknObject *new(const mknClass *class, ...) -{ - va_list list; - mknObject *new = NULL; - - if (!class) { - KN_PANIC("Bad parameter"); - } - va_start(list, class); - new = va_new(class, &list); - va_end(list); - return new; -} - -mknObject *va_new(const mknClass *class, va_list *ap) -{ - mknObject *new = NULL; - - if (!class || !ap) { - KN_PANIC("Bad parameter"); - } - new = malloc(class->__size__); - if (!new) { - KN_PANIC("Memory allocation error"); - } - memcpy(new, class, class->__size__); - if (class->__ctor__) { - class->__ctor__(new, ap); - } - return new; -} - -void delete(mknObject *ptr) -{ - if (!ptr) { - KN_PANIC("Bad parameter"); - } - if (((mknClass *)ptr)->__dtor__) { - ((mknClass *)ptr)->__dtor__(ptr); - } - free(ptr); -} diff --git a/src/mkn/server.c b/src/mkn/server.c deleted file mode 100644 index 44c5b9d..0000000 --- a/src/mkn/server.c +++ /dev/null @@ -1,173 +0,0 @@ -/* -** FREE PROJECT, 2026 -** KRONKNET -** File description: -** Server impl for MKN -*/ -#include "kronknet/server/server.h" -#include "kronknet/callback/callback.h" -#include "kronknet/macros/errdef.h" -#include "kronknet/mkn/object.h" -#include "kronknet/mkn/server.h" -#include -#include -#include -#include -#include -#include "../server/server.h" - -typedef struct { - - mknServerContainer base; - knServer* _server; - -} mknServerClass; - -static void mknServer_ctor(mknServerClass *thus, va_list *args) -{ - if (!thus || !args) { - KN_PANIC("Bad parameter"); - } - thus->_server = knServer_create(va_arg(*args, int), 0); - if (!thus->_server) { - KN_PANIC("[mknServer]: Failed to initialize internal ressources"); - } -} - -static void mknServer_dtor(mknServerClass *thus) -{ - if (thus) { - knServer_destroy(thus->_server); - } -} - -static char *mknServer_toString(mknServerClass *thus) -{ - size_t size = 0; - char *buffer = 0; - - if (!thus) { - KN_PANIC("Bad parameter"); - } - size = snprintf(NULL, 0, "<%s (localhost:%d)>", thus->base.base.__name__, port(&thus->base)); - buffer = malloc(sizeof(char) * (size + 1)); - if (!buffer) { - KN_PANIC("Memory allocation failed"); - } - snprintf(buffer, size + 1, "<%s (localhost:%d)>", thus->base.base.__name__, port(&thus->base)); - return buffer; -} - -static int mknServer_run(mknServerClass *thus) -{ - if (!thus) { - KN_PANIC("Bad parameter"); - } - return knServer_run(thus->_server); -} - -static int mknServer_runOnce(mknServerClass *thus, ssize_t timeout) -{ - if (!thus) { - KN_PANIC("Bad parameter"); - } - return knServer_runOnce(thus->_server, timeout); -} - -static int mknServer_onReadCb(mknServerClass *thus, knServer_OnRead_t cb) -{ - if (!thus) { - KN_PANIC("Bad parameter"); - } - return knServer_setOnRead(thus->_server, cb); -} - -static int mknServer_onWriteCb(mknServerClass *thus, knServer_OnWrite_t cb) -{ - if (!thus) { - KN_PANIC("Bad parameter"); - } - return knServer_setOnWrite(thus->_server, cb); -} - -static int mknServer_onConnectCb(mknServerClass *thus, knServer_OnConnect_t cb) -{ - if (!thus) { - KN_PANIC("Bad parameter"); - } - return knServer_setOnConnect(thus->_server, cb); -} - -static int mknServer_onDisconnectCb(mknServerClass *thus, knServer_OnDisconnect_t cb) -{ - if (!thus) { - KN_PANIC("Bad parameter"); - } - return knServer_setOnDisconnect(thus->_server, cb); -} - -static int mknServer_getPort(mknServerClass *thus) -{ - if (!thus) { - KN_PANIC("Bad parameter"); - } - return ntohs(thus->_server->addr.sin_port); -} - -static int mknServer_setLogging(mknServerClass *thus, ...) -{ - va_list l; - - if (!thus) { - KN_PANIC("Bad parameter"); - } - va_start(l, thus); - bool c = va_arg(l, int); - va_end(l); - knServer_setLogLevel(thus->_server, c); - return KNEVTOK; -} - -static int mknServer_setUserPtr(mknServerClass *thus, ...) -{ - va_list l; - - if (!thus) { - KN_PANIC("Bad parameter"); - } - va_start(l, thus); - knServer_setUserPtr(thus->_server, va_arg(l, void *)); - va_end(l); - return KNEVTOK; -} - -static const mknServerClass _description = { - { - { - .__size__ = sizeof(_description), - .__name__ = "mknServer", - .__ctor__ = (ctor_t)&mknServer_ctor, - .__dtor__ = (dtor_t)&mknServer_dtor, - .__str__ = (to_string_t)&mknServer_toString, - .__add__ = NULL, - .__sub__ = NULL, - .__mul__ = NULL, - .__div__ = NULL, - .__eq__ = NULL, - .__gt__ = NULL, - .__lt__ = NULL, - }, - .__run__ = (mkn_run_t)&mknServer_run, - .__runOnce__ = (mkn_runOnce_t)&mknServer_runOnce, - .__onConnect__ = (mkn_setCb_t)&mknServer_onConnectCb, - .__onRead__ = (mkn_setCb_t)&mknServer_onReadCb, - .__onWrite__ = (mkn_setCb_t)&mknServer_onWriteCb, - .__onDisconnect__ = (mkn_setCb_t)&mknServer_onDisconnectCb, - .__port__ = (mkn_getInt_t)&mknServer_getPort, - .__log__ = (mkn_set_t)&mknServer_setLogging, - .__data__ = (mkn_set_t)&mknServer_setUserPtr, - }, - ._server = NULL, -}; - -const mknClass *mknServer = (const mknClass *)&_description; From 6a19b051eccfa2d7a104920388793c769c4728a7 Mon Sep 17 00:00:00 2001 From: GauthierMalfilatre Date: Wed, 23 Sep 2026 23:50:15 +0200 Subject: [PATCH 13/14] [UP] The knServer is now evolving around a epoll() call. --- src/connection/connection.h | 4 +-- src/connection/hooks/tcp/conn_tcpSend.c | 4 +-- src/connection/hooks/udp/conn_udpSend.c | 4 +-- src/connection/init/conn_create.c | 1 + src/connection/setters/conn_setEvents.c | 14 ++++++-- src/server/hooks/tcp/server_kick.c | 12 ++++--- src/server/hooks/tcp/server_tcpPollin.c | 18 +++++----- src/server/hooks/tcp/server_tcpPollout.c | 7 ++-- src/server/hooks/tcp/tcp.h | 4 +-- src/server/hooks/udp/server_udpPollin.c | 7 ++-- src/server/hooks/udp/server_udpPollout.c | 6 ++-- src/server/hooks/udp/udp.h | 4 +-- src/server/init/server_init.c | 21 ++++++++++-- src/server/pool/pool.h | 32 ++++++++++++++---- src/server/pool/pool_clear.c | 10 +++--- src/server/pool/pool_init.c | 13 ++++---- src/server/pool/pool_register.c | 42 +++++++++++++++++------- src/server/pool/pool_unregister.c | 17 +++++----- src/server/server.h | 22 +++++++++---- src/server/server_run.c | 42 +++++++++++++++--------- 20 files changed, 188 insertions(+), 96 deletions(-) diff --git a/src/connection/connection.h b/src/connection/connection.h index 2a2cbd0..e9f0cfb 100644 --- a/src/connection/connection.h +++ b/src/connection/connection.h @@ -39,7 +39,7 @@ typedef struct kronknet_connection_s { void *user_ptr; //!< The user datas (eg User struct ...) knRBuff *out_buff; //!< The out buffer knConnection_sendHook sendHook; //!< The send hook - short int *evtptr; //!< The ptr to the events in the pool + int epollfd; //!< The epoll fd of the server pool union { struct {} on_udp; //!< On UDP datas struct {} on_tcp; //!< On tcp datas @@ -54,6 +54,6 @@ void knConnection_destroy(knConnection *conn); knConnection *knConnection_create(const struct sockaddr_in* addr, knFlags flags); -int knConnection_setEvents(knConnection *conn, short int events); +int knConnection_setEvents(knConnection *conn, uint32_t events); #endif /* KRONKNET_CONNECTION_IMPL_H */ diff --git a/src/connection/hooks/tcp/conn_tcpSend.c b/src/connection/hooks/tcp/conn_tcpSend.c index 0f3f0df..6521f37 100644 --- a/src/connection/hooks/tcp/conn_tcpSend.c +++ b/src/connection/hooks/tcp/conn_tcpSend.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include @@ -44,6 +44,6 @@ int knConnection_tcpSendHook( if (knRBuff_push(conn->out_buff, data + written, remaining) == -1) { return KNEVTERR; } - knConnection_setEvents(conn, POLLOUT | POLLIN); + knConnection_setEvents(conn, EPOLLOUT | EPOLLIN); return KNEVTOK; } diff --git a/src/connection/hooks/udp/conn_udpSend.c b/src/connection/hooks/udp/conn_udpSend.c index b6f45a6..5bd9641 100644 --- a/src/connection/hooks/udp/conn_udpSend.c +++ b/src/connection/hooks/udp/conn_udpSend.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include @@ -44,6 +44,6 @@ int knConnection_udpSendHook( if (knRBuff_push(conn->out_buff, data + written, remaining) == -1) { return KNEVTERR; } - knConnection_setEvents(conn, POLLOUT | POLLIN); + knConnection_setEvents(conn, EPOLLOUT | EPOLLIN); return KNEVTOK; } diff --git a/src/connection/init/conn_create.c b/src/connection/init/conn_create.c index b63f805..9c08ac6 100644 --- a/src/connection/init/conn_create.c +++ b/src/connection/init/conn_create.c @@ -32,6 +32,7 @@ static void __knConnection_statics( conn->addr_length = sizeof(conn->addr); conn->fd = -1; + conn->epollfd = -1; conn->port = ntohs(conn->addr.sin_port); conn->id = id++; conn->last_data = monotonic(); diff --git a/src/connection/setters/conn_setEvents.c b/src/connection/setters/conn_setEvents.c index fea8e4e..c04e5ab 100644 --- a/src/connection/setters/conn_setEvents.c +++ b/src/connection/setters/conn_setEvents.c @@ -6,15 +6,25 @@ */ #include "../connection.h" #include "kronknet/macros/errdef.h" +#include "kronknet/macros/types.h" +#include +#include int knConnection_setEvents( knConnection *conn, - short int events + uint32_t events ) { + struct epoll_event ev; + if (!conn) { return KNEVTARGS; } - *(conn->evtptr) = events; + // NOTE: In UDP, conn->fd is the server socket, so data.ptr stays NULL + ev.events = events; + ev.data.ptr = (conn->flags & knUDP) ? NULL : conn; + if (epoll_ctl(conn->epollfd, EPOLL_CTL_MOD, conn->fd, &ev) == -1) { + return KNEVTNET; + } return KNEVTOK; } diff --git a/src/server/hooks/tcp/server_kick.c b/src/server/hooks/tcp/server_kick.c index dfd1fb9..f59d789 100644 --- a/src/server/hooks/tcp/server_kick.c +++ b/src/server/hooks/tcp/server_kick.c @@ -31,12 +31,16 @@ void knServer_kickAtIndex( size_t idx ) { - if (!server || idx == 0) + knConnection *conn; + + if (!server || idx == 0 || idx >= server->pool.count) return; + conn = server->pool.conns[idx]; if (server->onDisconnect) { - server->onDisconnect(server, server->pool.conns[idx]); + server->onDisconnect(server, conn); } - knInfo(server->logger, "Connection [%d]: Kicking...", server->pool.conns[idx]->fd); - knConnection_destroy(server->pool.conns[idx]); + knInfo(server->logger, "Connection [%d]: Kicking...", conn->fd); + // NOTE: Unregister before destroy, epoll needs the fd to be still open knPool_unregisterAtIndex(&server->pool, idx); + knConnection_destroy(conn); } diff --git a/src/server/hooks/tcp/server_tcpPollin.c b/src/server/hooks/tcp/server_tcpPollin.c index dd534fb..49192d0 100644 --- a/src/server/hooks/tcp/server_tcpPollin.c +++ b/src/server/hooks/tcp/server_tcpPollin.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include "kronknet/macros/errdef.h" #include "../../server.h" #include "../../../connection/connection.h" @@ -26,8 +26,9 @@ static int __knServer_accept( if (!newConn) return KNEVTERR; knInfo(server->logger, "Connection [%d] from %s:%d", newConn->fd, newConn->ip, newConn->port); - if (knPool_registerFd(&server->pool, newConn->fd, newConn, POLLIN) != KNEVTOK) { + if (knPool_registerFd(&server->pool, newConn->fd, newConn, EPOLLIN) != KNEVTOK) { knError(server->logger, "Connection [%d]: failed to add to pool", newConn->fd); + knConnection_destroy(newConn); return KNEVTERR; } knInfo(server->logger, "Connection [%d]: added to pool", newConn->fd); @@ -76,24 +77,23 @@ static int __knServer_receiveData( int knServer_tcpPollinHook( knServer* server, - size_t *idx + knConnection *conn ) { - if (server->pool.pollfds[*idx].fd == server->fd) { + if (!conn) { knInfo(server->logger, "New connection request received"); if (__knServer_accept(server) != KNEVTOK) { knError(server->logger, "Connection request declined"); } } else { knInfo(server->logger, "Data received"); - switch (__knServer_receiveData(server, server->pool.conns[*idx])) { + switch (__knServer_receiveData(server, conn)) { case KNEVTERR: - knError(server->logger, "Connection [%d]: Error while receiving data", server->pool.conns[*idx]->id); + knError(server->logger, "Connection [%d]: Error while receiving data", conn->id); break; case KNEVTKICK: - knServer_kickAtIndex(server, *idx); - (*idx)--; - break; + knServer_kick(server, conn); + return KNEVTKICK; default: break; } diff --git a/src/server/hooks/tcp/server_tcpPollout.c b/src/server/hooks/tcp/server_tcpPollout.c index e5f6b59..e4f21b3 100644 --- a/src/server/hooks/tcp/server_tcpPollout.c +++ b/src/server/hooks/tcp/server_tcpPollout.c @@ -5,7 +5,7 @@ ** Server tcp pollout hook */ #include -#include +#include #include "kronknet/macros/errdef.h" #include "../../server.h" #include "../../../connection/connection.h" @@ -13,10 +13,9 @@ int knServer_tcpPolloutHook( knServer* server, - size_t *idx + knConnection *conn ) { - knConnection *conn = server->pool.conns[*idx]; uint8_t tmp[KNBUFFSIZ] = {}; size_t usage = knRBuff_usage(conn->out_buff); @@ -27,7 +26,7 @@ int knServer_tcpPolloutHook( knRBuff_pop(conn->out_buff, NULL, sends); knInfo(server->logger, "Connection [%d]: sent %zu bytes, remaining: %zu bytes.", conn->id, (size_t)sends, knRBuff_usage(conn->out_buff)); if (knRBuff_isEmpty(conn->out_buff)) { - knConnection_setEvents(server->pool.conns[*idx], POLLIN); + knConnection_setEvents(conn, EPOLLIN); if (server->onWrite) { server->onWrite(conn); } diff --git a/src/server/hooks/tcp/tcp.h b/src/server/hooks/tcp/tcp.h index f26ebf4..cf9493e 100644 --- a/src/server/hooks/tcp/tcp.h +++ b/src/server/hooks/tcp/tcp.h @@ -8,8 +8,8 @@ #define KRONKNET_SERVER_TCP_H #include "kronknet/callback/callback.h" -int knServer_tcpPolloutHook(knServer* server, size_t *idx); -int knServer_tcpPollinHook(knServer* server, size_t *idx); +int knServer_tcpPolloutHook(knServer* server, knConnection *conn); +int knServer_tcpPollinHook(knServer* server, knConnection *conn); void knServer_tcpCleanupHook(knServer* server); #endif /* KRONKNET_SERVER_TCP_H */ diff --git a/src/server/hooks/udp/server_udpPollin.c b/src/server/hooks/udp/server_udpPollin.c index fdc7939..d4591b0 100644 --- a/src/server/hooks/udp/server_udpPollin.c +++ b/src/server/hooks/udp/server_udpPollin.c @@ -8,14 +8,13 @@ #include "kronknet/macros/errdef.h" #include "kronknet/macros/optimization.h" #include -#include #include "../../../connection/connection.h" #include "kronknet/utils/monotonic.h" #include "../../../server/server.h" int knServer_udpPollinHook( knServer* server, - size_t *idx KN_UNUSED + knConnection *evtconn KN_UNUSED ) { struct sockaddr_in addr = {}; @@ -32,10 +31,10 @@ int knServer_udpPollinHook( conn = knMap_search(server->on_udp.connections, key); if (!conn) { conn = knConnection_create(&addr, server->flags); - conn->fd = server->fd; - conn->evtptr = &server->pool.pollfds[0].events; if (!conn) return KNEVTMEM; + conn->fd = server->fd; + conn->epollfd = server->pool.epollfd; if (knMap_insert(server->on_udp.connections, key, conn, (knMapDeleter)&knConnection_destroy) == -1) return KNEVTERR; diff --git a/src/server/hooks/udp/server_udpPollout.c b/src/server/hooks/udp/server_udpPollout.c index 1c8376b..b7a98e2 100644 --- a/src/server/hooks/udp/server_udpPollout.c +++ b/src/server/hooks/udp/server_udpPollout.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include "kronknet/utils/hashmap/hashmap.h" #include "kronknet/utils/rbuff/rbuff.h" @@ -59,7 +59,7 @@ static void __pollout( int knServer_udpPolloutHook( knServer* server, - size_t *idx KN_UNUSED + knConnection *evtconn KN_UNUSED ) { knPolloutContext ctx = { .server = server, .packets_remaining = false }; @@ -69,7 +69,7 @@ int knServer_udpPolloutHook( } knMap_foreach(server->on_udp.connections, &__pollout, &ctx); if (!ctx.packets_remaining) { - server->pool.pollfds[0].events = POLLIN; + knPool_modifyFd(&server->pool, server->fd, NULL, EPOLLIN); } return KNEVTOK; } diff --git a/src/server/hooks/udp/udp.h b/src/server/hooks/udp/udp.h index 5c8ac9e..9abd6b5 100644 --- a/src/server/hooks/udp/udp.h +++ b/src/server/hooks/udp/udp.h @@ -8,8 +8,8 @@ #define KRONKNET_SERVER_UDP_H #include "kronknet/callback/callback.h" -int knServer_udpPolloutHook(knServer* server, size_t *idx); -int knServer_udpPollinHook(knServer* server, size_t *idx); +int knServer_udpPolloutHook(knServer* server, knConnection *conn); +int knServer_udpPollinHook(knServer* server, knConnection *conn); void knServer_udpCleanupHook(knServer* server); void knServer_udpDestroyHook(knServer* server); diff --git a/src/server/init/server_init.c b/src/server/init/server_init.c index c2de71f..89dd052 100644 --- a/src/server/init/server_init.c +++ b/src/server/init/server_init.c @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include @@ -77,6 +77,7 @@ static void __knServer_basics( { server->flags = flags; server->running = true; + server->pool.epollfd = -1; server->onConnection = NULL; server->onWrite = NULL; server->onRead = NULL; @@ -126,18 +127,23 @@ int knServer_init( } if (flags & knTCP) type = SOCK_STREAM; else if (flags & knUDP) type = SOCK_DGRAM; + server->fd = socket(AF_INET, type, 0); - if (server->fd == -1) + + if (server->fd == -1) { return KNEVTNET; + } if (__knServer_nonBlocking(server->fd) != KNEVTOK) { close(server->fd); return KNEVTNET; } + if (__knServer_bind(server, port) != KNEVTOK) { close(server->fd); return KNEVTNET; } + if (type == SOCK_STREAM) { if (listen(server->fd, SOMAXCONN) == -1) { close(server->fd); @@ -149,12 +155,21 @@ int knServer_init( close(server->fd); return KNEVTERR; } + if (server->flags & knUDP) { server->on_udp.connections = knMap_create(knMap_basicHash, 8); if (!server->on_udp.connections) { + knPool_clear(&server->pool); close(server->fd); return KNEVTMEM; } } - return knPool_registerFd(&server->pool, server->fd, NULL, POLLIN); + + // NOTE: The server is registered with a NULL connection (data.ptr) + if (knPool_registerFd(&server->pool, server->fd, NULL, EPOLLIN) != KNEVTOK) { + knPool_clear(&server->pool); + close(server->fd); + return KNEVTNET; + } + return KNEVTOK; } diff --git a/src/server/pool/pool.h b/src/server/pool/pool.h index e7155c5..eff1cad 100644 --- a/src/server/pool/pool.h +++ b/src/server/pool/pool.h @@ -2,11 +2,12 @@ ** FREE PROJECT, 2026 ** KRONKNET ** File description: -** Pool of pollfds struct definition +** Pool of epoll watched fds struct definition */ #ifndef KRONKNET_POOL_H #define KRONKNET_POOL_H #include + #include #include #include #include @@ -17,15 +18,18 @@ typedef struct kronknet_connection_s knConnection; /** * @struct knonknet_pool_s * - * @brief pollfds pool struct, with size and capacity + * @brief epoll pool struct, with the connections, size and capacity + * + * @note The server socket is registered with a NULL connection + * (epoll_event.data.ptr == NULL) */ /////////////////////////////////////////////////////////////////////////////// typedef struct kronknet_pool_s { - struct pollfd *pollfds; //!< The array of poolfd + int epollfd; //!< The fd of epoll knConnection **conns; //!< The connections size_t size; //!< The capacity of the pool - size_t count; //!< The number of pollfd in the pool + size_t count; //!< The number of fds in the pool } knPool; /////////////////////////////////////////////////////////////////////////////// @@ -37,11 +41,27 @@ typedef struct kronknet_pool_s { * * @param pool The pool in which [fd] will be register * @param fd The file descriptor to register - * @param events The events of [fd] + * @param conn The connection of [fd] (NULL for the server) + * @param events The epoll events of [fd] + * @return 0 on success, -1 otherwise + */ +/////////////////////////////////////////////////////////////////////////////// +int knPool_registerFd(knPool *pool, int fd, knConnection *conn, uint32_t events); +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/** + * @brief Modify the watched events of [fd] inside [pool] + * + * @param pool The pool in which [fd] is registered + * @param fd The file descriptor to modify + * @param conn The connection of [fd] (NULL for the server) + * @param events The new epoll events of [fd] * @return 0 on success, -1 otherwise */ /////////////////////////////////////////////////////////////////////////////// -int knPool_registerFd(knPool *pool, int fd, knConnection *conn, int events); +int knPool_modifyFd(knPool *pool, int fd, knConnection *conn, uint32_t events); /////////////////////////////////////////////////////////////////////////////// diff --git a/src/server/pool/pool_clear.c b/src/server/pool/pool_clear.c index ec62b9e..dc8a0b9 100644 --- a/src/server/pool/pool_clear.c +++ b/src/server/pool/pool_clear.c @@ -7,7 +7,7 @@ #include "pool.h" #include #include -#include +#include #include "../../connection/connection.h" void knPool_clear( @@ -17,14 +17,16 @@ void knPool_clear( if (!pool) { return; } - if (pool->pollfds) { - free(pool->pollfds); - } if (pool->conns) { for (size_t i = 0; i < pool->count; ++i){ knConnection_destroy(pool->conns[i]); } free(pool->conns); + pool->conns = NULL; + } + if (pool->epollfd != -1) { + close(pool->epollfd); + pool->epollfd = -1; } pool->count = 0; pool->size = 0; diff --git a/src/server/pool/pool_init.c b/src/server/pool/pool_init.c index e6c5e02..4945bc7 100644 --- a/src/server/pool/pool_init.c +++ b/src/server/pool/pool_init.c @@ -8,8 +8,8 @@ #include "kronknet/macros/errdef.h" #include "pool.h" #include -#include #include +#include int knPool_init( knPool *pool @@ -20,15 +20,16 @@ int knPool_init( } pool->count = 0; pool->size = 1; - pool->pollfds = calloc(pool->size, sizeof(struct pollfd)); - if (!pool->pollfds) { - return KNEVTMEM; - } pool->conns = calloc(pool->size, sizeof(knConnection *)); if (!pool->conns) { - free(pool->pollfds); return KNEVTMEM; } pool->conns[0] = NULL; + pool->epollfd = epoll_create1(EPOLL_CLOEXEC); + if (pool->epollfd == -1) { + free(pool->conns); + pool->conns = NULL; + return KNEVTNET; + } return KNEVTOK; } diff --git a/src/server/pool/pool_register.c b/src/server/pool/pool_register.c index f25449c..2793b4c 100644 --- a/src/server/pool/pool_register.c +++ b/src/server/pool/pool_register.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include "../../connection/connection.h" static int __knPool_ensureCapacity( @@ -23,10 +23,6 @@ static int __knPool_ensureCapacity( return KNEVTARGS; } while (pool->size < size) { - pool->pollfds = realloc(pool->pollfds, sizeof(struct pollfd) * pool->size * 2); - if (pool->pollfds == NULL) - return KNEVTMEM; - memset(&pool->pollfds[pool->size], 0, sizeof(struct pollfd) * pool->size); pool->conns = realloc(pool->conns, sizeof(knConnection *) * pool->size * 2); if (pool->conns == NULL) return KNEVTMEM; @@ -40,10 +36,14 @@ int knPool_registerFd( knPool *pool, knSocket fd, knConnection *conn, - int events + uint32_t events ) { size_t new_count = 0; + struct epoll_event ev = { + .events = events, + .data.ptr = conn, + }; if (!pool || fd == -1) { return KNEVTARGS; @@ -53,14 +53,34 @@ int knPool_registerFd( if (err != KNEVTOK) { return err; } - pool->pollfds[pool->count].fd = fd; - pool->pollfds[pool->count].events = events; + if (epoll_ctl(pool->epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) { + return KNEVTNET; + } pool->conns[pool->count] = conn; if (conn) { - if (conn) { - conn->evtptr = &pool->pollfds[pool->count].events; - } + conn->epollfd = pool->epollfd; } pool->count = new_count; return KNEVTOK; } + +int knPool_modifyFd( + knPool *pool, + knSocket fd, + knConnection *conn, + uint32_t events +) +{ + struct epoll_event ev = { + .events = events, + .data.ptr = conn, + }; + + if (!pool || fd == -1) { + return KNEVTARGS; + } + if (epoll_ctl(pool->epollfd, EPOLL_CTL_MOD, fd, &ev) == -1) { + return KNEVTNET; + } + return KNEVTOK; +} diff --git a/src/server/pool/pool_unregister.c b/src/server/pool/pool_unregister.c index 361b2df..cdd1282 100644 --- a/src/server/pool/pool_unregister.c +++ b/src/server/pool/pool_unregister.c @@ -2,7 +2,7 @@ ** FREE PROJECT, 2026 ** KRONKNET ** File description: -** Unregister an fd for the server's poll +** Unregister an fd for the server's epoll */ #include "kronknet/macros/errdef.h" #include "kronknet/macros/types.h" @@ -10,7 +10,8 @@ #include #include #include -#include +#include +#include "../../connection/connection.h" int knPool_unregister( knPool *pool, @@ -21,7 +22,7 @@ int knPool_unregister( return KNEVTERR; } for (size_t i = 0; i < pool->count; ++i) { - if (pool->pollfds[i].fd == fd) { + if (pool->conns[i] && pool->conns[i]->fd == fd) { return knPool_unregisterAtIndex(pool, i); } } @@ -36,13 +37,11 @@ int knPool_unregisterAtIndex( if (!pool || index == -1UL || index >= pool->count) { return KNEVTERR; } - pool->pollfds[index] = pool->pollfds[pool->count - 1]; - pool->pollfds[pool->count - 1] = (struct pollfd){ - -1, - 0, - 0 - }; + if (pool->conns[index] && pool->conns[index]->fd != -1) { + epoll_ctl(pool->epollfd, EPOLL_CTL_DEL, pool->conns[index]->fd, NULL); + } pool->conns[index] = pool->conns[pool->count - 1]; pool->conns[pool->count - 1] = NULL; + pool->count--; return KNEVTOK; } diff --git a/src/server/server.h b/src/server/server.h index 223f8b1..5a46e3c 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -10,29 +10,35 @@ #include "kronknet/macros/types.h" #include #include -#include + #include #include "kronknet/utils/hashmap/hashmap.h" #include "pool/pool.h" #include "kronknet/macros/types.h" #include "../utils/logger/logger.h" + #define KN_MAX_EVENTS 1024 //!< Max events returned by one epoll_wait + /////////////////////////////////////////////////////////////////////////////// /** * @brief Declararion of pollin hook * - * @note idx does not serve in UDP mode + * @note conn is the epoll data.ptr: NULL means the server socket. + * conn does not serve in UDP mode + * @return KNEVTKICK if conn has been kicked (and destroyed) */ /////////////////////////////////////////////////////////////////////////////// -typedef int (*knServer_onPollinHook)(knServer *server, size_t *idx); +typedef int (*knServer_onPollinHook)(knServer *server, knConnection *conn); /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /** * @brief Declaration of pollout hook + * + * @note conn does not serve in UDP mode */ /////////////////////////////////////////////////////////////////////////////// -typedef int (*knServer_onPolloutHook)(knServer *server, size_t *idx); +typedef int (*knServer_onPolloutHook)(knServer *server, knConnection *conn); /////////////////////////////////////////////////////////////////////////////// @@ -58,7 +64,7 @@ typedef void (*knServer_onDestructionHook)(knServer *server); /** * @struct server_s * - * @brief Server structure, containing necessary datas: pollfds, addr, ... + * @brief Server structure, containing necessary datas: epoll pool, addr, ... */ /////////////////////////////////////////////////////////////////////////////// typedef struct kronknet_server_s { @@ -67,15 +73,19 @@ typedef struct kronknet_server_s { knBool running; //!< Is the server running knSocket fd; //!< The fd of the server socket struct sockaddr_in addr; //!< The address of the server - knPool pool; //!< The pool of pollfds to look on + knPool pool; //!< The epoll pool of fds to look on void* user_ptr; //!< Data like a struct given by the user + knServer_OnConnect_t onConnection; //!< onConnection callback knServer_OnRead_t onRead; //!< onRead callback knServer_OnWrite_t onWrite; //!< onWrite callback knServer_OnDisconnect_t onDisconnect; //!< onDisconnect callback + char ip[INET_ADDRSTRLEN]; //!< The ip as a string + knLoggerData logger; //!< The logger data uint64_t connection_timeout; //!< The max seconds the connections can rest withoud sending data (default: UDP = 30,000, TCP = 180,000) + union { struct {} on_tcp; //!< Specific on TCP (maybe later were gonna add smth) diff --git a/src/server/server_run.c b/src/server/server_run.c index 948d4c9..35bf7b6 100644 --- a/src/server/server_run.c +++ b/src/server/server_run.c @@ -13,25 +13,37 @@ #include #include #include -#include +#include #include #include #include "server.h" KN_HOT -static int __knServer_processPoll( - knServer *server +static int __knServer_processEvents( + knServer *server, + const struct epoll_event *events, + int nfds ) { - for (size_t i = 0; i < server->pool.count; ++i) { - if (server->pool.pollfds[i].revents & POLLIN && - server->onPollinHook && - server->onPollinHook(server, &i) != KNEVTOK) { - return KNEVTERR; + knConnection *conn; + int status; + + for (int i = 0; i < nfds; ++i) { + // NOTE: NULL means the server socket + conn = events[i].data.ptr; + if (events[i].events & (EPOLLIN | EPOLLHUP | EPOLLERR) && + server->onPollinHook) { + status = server->onPollinHook(server, conn); + if (status == KNEVTKICK) { + continue; + } + if (status != KNEVTOK) { + return KNEVTERR; + } } - if (server->pool.pollfds[i].revents & POLLOUT && + if (events[i].events & EPOLLOUT && server->onPolloutHook && - server->onPolloutHook(server, &i) != KNEVTOK) { + server->onPolloutHook(server, conn) != KNEVTOK) { return KNEVTERR; } } @@ -45,17 +57,17 @@ int knServer_runOnce( ssize_t timeoutMs ) { - int status; + struct epoll_event events[KN_MAX_EVENTS]; + int nfds; if (!server) { return KNEVTARGS; } - // FIXME: Maybe epoll is better ? - status = poll(server->pool.pollfds, server->pool.count, timeoutMs); - if (status == -1) { + nfds = epoll_wait(server->pool.epollfd, events, KN_MAX_EVENTS, (int)timeoutMs); + if (nfds == -1) { return KNEVTNET; } - if (__knServer_processPoll(server) != KNEVTOK) { + if (__knServer_processEvents(server, events, nfds) != KNEVTOK) { return KNEVTERR; } if (server->onCleanupHook) { From 92e47d5caf73ff070c54d6c846d9d70bedff6799 Mon Sep 17 00:00:00 2001 From: GauthierMalfilatre Date: Wed, 23 Sep 2026 23:52:05 +0200 Subject: [PATCH 14/14] [ADD] Add some tests --- test/epoll/test.c | 60 ++++++ tests/CMakeLists.txt | 10 +- tests/network/net_utils.h | 263 +++++++++++++++++++++++++ tests/network/tests_tcp_server.c | 328 +++++++++++++++++++++++++++++++ tests/network/tests_udp_server.c | 138 +++++++++++++ 5 files changed, 798 insertions(+), 1 deletion(-) create mode 100644 test/epoll/test.c create mode 100644 tests/network/net_utils.h create mode 100644 tests/network/tests_tcp_server.c create mode 100644 tests/network/tests_udp_server.c diff --git a/test/epoll/test.c b/test/epoll/test.c new file mode 100644 index 0000000..78149b8 --- /dev/null +++ b/test/epoll/test.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include + +#define MAX_EVENTS 1024 + +int main( + int argc, + char *const *argv +) +{ + + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr = {}; + + if (bind(fd, (const struct sockaddr *)&addr, sizeof(addr)) == -1) { + close(fd); + return 84; + } + + if (listen(fd, SOMAXCONN) == -1) { + close(fd); + return 84; + } + + int epollfd = epoll_create1(0); + + if (epollfd == -1) { + return 84; + } + + struct epoll_event ev, events[MAX_EVENTS]; + + ev.events = EPOLLIN; + ev.data.fd = fd; + + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, events) == -1) { + close(fd); + close(epollfd); + } + + for (;;) { + ssize_t ndfs = epoll_wait(epollfd, events, MAX_EVENTS, -1); + + if (ndfs == -1) { + close(fd); + close(epollfd); + return 84; + } + + for (ssize_t n = 0; n < ndfs; ++n) { + if (events[n].data.fd == fd) { + + } + } + } + +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f5146c7..4ef675b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,7 +11,15 @@ FetchContent_Declare( FetchContent_MakeAvailable(kronklab) -file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/utils/*.c) +# NOTE: Build kronknet from this repository, not the installed one +if(NOT TARGET kronknet) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/kronknet) +endif() + +file(GLOB_RECURSE SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/utils/*.c + ${CMAKE_CURRENT_SOURCE_DIR}/network/*.c +) add_executable(kronknet_tests ${SOURCES}) diff --git a/tests/network/net_utils.h b/tests/network/net_utils.h new file mode 100644 index 0000000..01ce490 --- /dev/null +++ b/tests/network/net_utils.h @@ -0,0 +1,263 @@ +/* +** FREE PROJECT, 2026 +** KRONKNET +** File description: +** Helpers for the network tests (server pump, test clients, callbacks) +*/ +#ifndef KRONKNET_TESTS_NET_UTILS_H + #define KRONKNET_TESTS_NET_UTILS_H + #include + #include "kronknet/callback/callback.h" + #include "kronknet/connection/connection.h" + #include "kronknet/macros/errdef.h" + #include "kronknet/macros/types.h" + #include "kronknet/server/server.h" + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #define NET_MAX_CONNS 64 //!< Max connections tracked by the callbacks + #define NET_TIMEOUT_MS 5000 //!< Max time to wait for something to happen + +/////////////////////////////////////////////////////////////////////////////// +/** + * @brief What the callbacks saw, and how they should behave. + * + * @note Each kronklab test runs in its own fork, so this is fresh per test + */ +/////////////////////////////////////////////////////////////////////////////// +typedef struct net_state_s { + + int connects; //!< onConnect calls + int reads; //!< onRead calls + int writes; //!< onWrite calls + int disconnects; //!< onDisconnect calls + size_t read_bytes; //!< Total bytes received + knConnection *conns[NET_MAX_CONNS]; //!< Connections, in accept order + int onConnect_ret; //!< Returned by onConnect + knBool echo; //!< Send back what is received + knBool disconnect_on_read; //!< knConnection_disconnect on read + +} net_state; + +static net_state g_net; + +static inline int net_onConnect(knServer *server, knConnection *conn) +{ + (void)server; + if (g_net.connects < NET_MAX_CONNS) { + g_net.conns[g_net.connects] = conn; + } + g_net.connects++; + return g_net.onConnect_ret; +} + +static inline int net_onRead(knConnection *conn, const void *data, size_t size) +{ + g_net.reads++; + g_net.read_bytes += size; + if (g_net.echo) { + knConnection_send(conn, data, size); + } + if (g_net.disconnect_on_read) { + knConnection_disconnect(conn); + } + return 0; +} + +static inline int net_onWrite(knConnection *conn) +{ + (void)conn; + g_net.writes++; + return 0; +} + +static inline int net_onDisconnect(knServer *server, knConnection *conn) +{ + (void)server; + (void)conn; + g_net.disconnects++; + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +/** + * @brief Create a server on [port] with the test callbacks (echo enabled) + */ +/////////////////////////////////////////////////////////////////////////////// +static inline knServer *net_server(knPort port, knFlags flags) +{ + knServer *server = knServer_create(port, flags); + + AssertNotNull(server, "Server creation on port %d should succeed", port); + memset(&g_net, 0, sizeof(g_net)); + g_net.onConnect_ret = KNEVTOK; + g_net.echo = knTrue; + knServer_setOnConnect(server, &net_onConnect); + knServer_setOnRead(server, &net_onRead); + knServer_setOnWrite(server, &net_onWrite); + knServer_setOnDisconnect(server, &net_onDisconnect); + return server; +} + +/////////////////////////////////////////////////////////////////////////////// +/** + * @brief Run the server for [ms] milliseconds + */ +/////////////////////////////////////////////////////////////////////////////// +static inline void net_pump(knServer *server, timestamp ms) +{ + timestamp end = kl_monotonic() + ms; + + while (kl_monotonic() < end) { + AssertEq(knServer_runOnce(server, 5), KNEVTOK, "runOnce should succeed"); + } +} + +/////////////////////////////////////////////////////////////////////////////// +/** + * @brief Run the server until [cond] is true, fail after NET_TIMEOUT_MS + */ +/////////////////////////////////////////////////////////////////////////////// + #define NET_PUMP_UNTIL(server, cond) \ + do { \ + timestamp __net_end = kl_monotonic() + NET_TIMEOUT_MS; \ + while (!(cond)) { \ + Assert(kl_monotonic() < __net_end, "Timeout waiting for: " #cond); \ + AssertEq(knServer_runOnce(server, 5), KNEVTOK, "runOnce should succeed"); \ + } \ + } while (0) + +static inline void net_setRecvTimeout(int fd, int ms) +{ + struct timeval tv = { .tv_sec = ms / 1000, .tv_usec = (ms % 1000) * 1000 }; + + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); +} + +static inline struct sockaddr_in net_loopback(knPort port) +{ + struct sockaddr_in addr = {0}; + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + return addr; +} + +/////////////////////////////////////////////////////////////////////////////// +/** + * @brief Connect a TCP client to 127.0.0.1:[port] + * + * @param rcvbuf SO_RCVBUF to set before connecting (0 to keep the default) + */ +/////////////////////////////////////////////////////////////////////////////// +static inline int net_tcpClientBuf(knPort port, int rcvbuf) +{ + struct sockaddr_in addr = net_loopback(port); + int fd = socket(AF_INET, SOCK_STREAM, 0); + + Assert(fd != -1, "Client socket creation should succeed"); + if (rcvbuf > 0) { + setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); + } + net_setRecvTimeout(fd, 1000); + Assert(connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == 0, + "Client should connect to port %d", port); + return fd; +} + +static inline int net_tcpClient(knPort port) +{ + return net_tcpClientBuf(port, 0); +} + +static inline int net_udpClient(void) +{ + int fd = socket(AF_INET, SOCK_DGRAM, 0); + + Assert(fd != -1, "UDP client socket creation should succeed"); + net_setRecvTimeout(fd, 1000); + return fd; +} + +static inline void net_udpSend(int fd, knPort port, const void *data, size_t size) +{ + struct sockaddr_in addr = net_loopback(port); + + AssertEq(sendto(fd, data, size, 0, (struct sockaddr *)&addr, sizeof(addr)), + (ssize_t)size, "UDP client should send the whole datagram"); +} + +static inline void net_send(int fd, const void *data, size_t size) +{ + AssertEq(send(fd, data, size, MSG_NOSIGNAL), (ssize_t)size, + "Client should send the whole message"); +} + +/////////////////////////////////////////////////////////////////////////////// +/** + * @brief Receive exactly [size] bytes on [fd], running the server meanwhile + */ +/////////////////////////////////////////////////////////////////////////////// +static inline void net_recvAll(knServer *server, int fd, void *buf, size_t size) +{ + timestamp end = kl_monotonic() + NET_TIMEOUT_MS; + size_t got = 0; + ssize_t r; + + while (got < size) { + r = recv(fd, (uint8_t *)buf + got, size - got, MSG_DONTWAIT); + if (r > 0) { + got += (size_t)r; + continue; + } + Assert(r != 0, "Connection closed after %zu/%zu bytes", got, size); + Assert(errno == EAGAIN || errno == EWOULDBLOCK, "recv failed (errno %d)", errno); + Assert(kl_monotonic() < end, "Timeout: received %zu/%zu bytes", got, size); + AssertEq(knServer_runOnce(server, 0), KNEVTOK, "runOnce should succeed"); + } +} + +/////////////////////////////////////////////////////////////////////////////// +/** + * @brief Receive one datagram on [fd], running the server meanwhile + */ +/////////////////////////////////////////////////////////////////////////////// +static inline ssize_t net_udpRecv(knServer *server, int fd, void *buf, size_t size) +{ + timestamp end = kl_monotonic() + NET_TIMEOUT_MS; + ssize_t r; + + while (1) { + r = recv(fd, buf, size, MSG_DONTWAIT); + if (r >= 0) { + return r; + } + Assert(errno == EAGAIN || errno == EWOULDBLOCK, "recv failed (errno %d)", errno); + Assert(kl_monotonic() < end, "Timeout waiting for a datagram"); + AssertEq(knServer_runOnce(server, 5), KNEVTOK, "runOnce should succeed"); + } +} + +/////////////////////////////////////////////////////////////////////////////// +/** + * @brief Is [fd] closed by the peer ? (EOF or reset, waits up to 1s) + */ +/////////////////////////////////////////////////////////////////////////////// +static inline knBool net_isClosed(int fd) +{ + char c; + ssize_t r = recv(fd, &c, 1, 0); + + return r == 0 || (r == -1 && errno == ECONNRESET); +} + +#endif /* KRONKNET_TESTS_NET_UTILS_H */ diff --git a/tests/network/tests_tcp_server.c b/tests/network/tests_tcp_server.c new file mode 100644 index 0000000..5a078a6 --- /dev/null +++ b/tests/network/tests_tcp_server.c @@ -0,0 +1,328 @@ +#include "net_utils.h" +#include +#include + +Test(tcp_server, create_destroy) +{ + knServer *server = knServer_create(42101, knTCP); + + AssertNotNull(server, "TCP server creation should succeed"); + Assert(knServer_isRunning(server), "Server should be running after creation"); + AssertEq(knServer_getPort(server), 42101, "Port should be the one given at creation"); + knServer_stop(server); + Assert(!knServer_isRunning(server), "Server should not be running after stop"); + knServer_destroy(server); +} + +Test(tcp_server, invalid_flags) +{ + AssertNull(knServer_create(42102, knTCP | knUDP), "TCP and UDP together should fail"); +} + +Test(tcp_server, port_already_used) +{ + knServer *server = knServer_create(42103, knTCP); + + AssertNotNull(server, "First server should be created"); + AssertNull(knServer_create(42103, knTCP), "Second server on the same port should fail"); + knServer_destroy(server); +} + +Test(tcp_server, runonce_null_server) +{ + AssertEq(knServer_runOnce(NULL, 0), KNEVTARGS, "runOnce(NULL) should return KNEVTARGS"); +} + +Test(tcp_server, idle_runonce_waits_timeout) +{ + knServer *server = net_server(42104, knTCP); + timestamp start = kl_monotonic(); + + AssertEq(knServer_runOnce(server, 100), KNEVTOK, "Idle runOnce should succeed"); + AssertGe(kl_monotonic() - start, (timestamp)90, "Idle runOnce should block until its timeout"); + AssertEq(g_net.connects, 0, "No connection should be reported"); + knServer_destroy(server); +} + +Test(tcp_server, accept_calls_onConnect) +{ + knServer *server = net_server(42105, knTCP); + int fd = net_tcpClient(42105); + struct sockaddr_in local = {0}; + socklen_t len = sizeof(local); + + NET_PUMP_UNTIL(server, g_net.connects == 1); + AssertNotNull(g_net.conns[0], "onConnect should receive the connection"); + getsockname(fd, (struct sockaddr *)&local, &len); + AssertEq(knConnection_getPort(g_net.conns[0]), ntohs(local.sin_port), + "Connection port should be the client's port"); + AssertStrEq(knConnection_getIp(g_net.conns[0]), "127.0.0.1", "Connection ip should be loopback"); + net_pump(server, 50); + AssertEq(g_net.connects, 1, "onConnect should be called only once"); + close(fd); + knServer_destroy(server); +} + +Test(tcp_server, echo) +{ + knServer *server = net_server(42106, knTCP); + int fd = net_tcpClient(42106); + const char msg[] = "hello kronknet"; + char buf[sizeof(msg)] = {0}; + + NET_PUMP_UNTIL(server, g_net.connects == 1); + net_send(fd, msg, sizeof(msg)); + net_recvAll(server, fd, buf, sizeof(msg)); + AssertEq(memcmp(buf, msg, sizeof(msg)), 0, "Echoed data should match"); + AssertEq(g_net.read_bytes, sizeof(msg), "onRead should have received every byte"); + close(fd); + knServer_destroy(server); +} + +Test(tcp_server, many_clients_get_their_own_echo) +{ + knServer *server = net_server(42107, knTCP); + int fds[20]; + char msg[16]; + char buf[16]; + + for (int i = 0; i < 20; ++i) { + fds[i] = net_tcpClient(42107); + } + NET_PUMP_UNTIL(server, g_net.connects == 20); + for (int i = 0; i < 20; ++i) { + snprintf(msg, sizeof(msg), "client-%02d", i); + net_send(fds[i], msg, 9); + } + for (int i = 0; i < 20; ++i) { + snprintf(msg, sizeof(msg), "client-%02d", i); + net_recvAll(server, fds[i], buf, 9); + AssertEq(memcmp(buf, msg, 9), 0, "Client %d should receive its own echo", i); + } + for (int i = 0; i < 20; ++i) { + close(fds[i]); + } + NET_PUMP_UNTIL(server, g_net.disconnects == 20); + knServer_destroy(server); +} + +Test(tcp_server, disconnect_calls_onDisconnect) +{ + knServer *server = net_server(42108, knTCP); + int fd = net_tcpClient(42108); + + NET_PUMP_UNTIL(server, g_net.connects == 1); + close(fd); + NET_PUMP_UNTIL(server, g_net.disconnects == 1); + net_pump(server, 50); + AssertEq(g_net.disconnects, 1, "onDisconnect should be called only once"); + knServer_destroy(server); +} + +Test(tcp_server, disconnect_keeps_others_alive) +{ + knServer *server = net_server(42109, knTCP); + int fds[6]; + char msg[16]; + char buf[16]; + + for (int i = 0; i < 6; ++i) { + fds[i] = net_tcpClient(42109); + } + NET_PUMP_UNTIL(server, g_net.connects == 6); + close(fds[1]); + close(fds[3]); + NET_PUMP_UNTIL(server, g_net.disconnects == 2); + for (int i = 0; i < 6; ++i) { + if (i == 1 || i == 3) + continue; + snprintf(msg, sizeof(msg), "still-%02d", i); + net_send(fds[i], msg, 8); + net_recvAll(server, fds[i], buf, 8); + AssertEq(memcmp(buf, msg, 8), 0, "Client %d should still get its echo", i); + } + fds[1] = net_tcpClient(42109); + NET_PUMP_UNTIL(server, g_net.connects == 7); + net_send(fds[1], "newcomer", 8); + net_recvAll(server, fds[1], buf, 8); + AssertEq(memcmp(buf, "newcomer", 8), 0, "A new client should work after disconnections"); + for (int i = 0; i < 6; ++i) { + if (i != 3) + close(fds[i]); + } + knServer_destroy(server); +} + +Test(tcp_server, connection_reset_is_kicked) +{ + knServer *server = net_server(42110, knTCP); + int fd = net_tcpClient(42110); + struct linger lg = { .l_onoff = 1, .l_linger = 0 }; + + NET_PUMP_UNTIL(server, g_net.connects == 1); + // NOTE: linger 0 makes close() send a RST (EPOLLHUP / EPOLLERR) + setsockopt(fd, SOL_SOCKET, SO_LINGER, &lg, sizeof(lg)); + close(fd); + NET_PUMP_UNTIL(server, g_net.disconnects == 1); + knServer_destroy(server); +} + +Test(tcp_server, onConnect_error_kicks_client) +{ + knServer *server = net_server(42111, knTCP); + int fd; + + g_net.onConnect_ret = KNEVTKICK; + fd = net_tcpClient(42111); + NET_PUMP_UNTIL(server, g_net.disconnects == 1); + AssertEq(g_net.connects, 1, "onConnect should have been called"); + Assert(net_isClosed(fd), "Client should be closed by the server"); + close(fd); + knServer_destroy(server); +} + +Test(tcp_server, inactivity_timeout_kicks_client) +{ + knServer *server = net_server(42112, knTCP); + timestamp start = kl_monotonic(); + int fd; + + knServer_setConnectionTimeout(server, 200); + fd = net_tcpClient(42112); + NET_PUMP_UNTIL(server, g_net.connects == 1); + NET_PUMP_UNTIL(server, g_net.disconnects == 1); + AssertGe(kl_monotonic() - start, (timestamp)200, "Client should not be kicked before the timeout"); + Assert(net_isClosed(fd), "Client should be closed by the server"); + close(fd); + knServer_destroy(server); +} + +Test(tcp_server, active_client_is_not_kicked) +{ + knServer *server = net_server(42113, knTCP); + char buf[4]; + int fd; + + knServer_setConnectionTimeout(server, 300); + fd = net_tcpClient(42113); + NET_PUMP_UNTIL(server, g_net.connects == 1); + for (int i = 0; i < 10; ++i) { + net_send(fd, "ping", 4); + net_recvAll(server, fd, buf, 4); + net_pump(server, 100); + } + AssertEq(g_net.disconnects, 0, "An active client should never be kicked"); + close(fd); + knServer_destroy(server); +} + +Test(tcp_server, disconnect_api_kicks_client) +{ + knServer *server = net_server(42114, knTCP); + int fd = net_tcpClient(42114); + + g_net.echo = knFalse; + g_net.disconnect_on_read = knTrue; + NET_PUMP_UNTIL(server, g_net.connects == 1); + net_send(fd, "bye", 3); + NET_PUMP_UNTIL(server, g_net.disconnects == 1); + Assert(net_isClosed(fd), "Client should be closed by the server"); + close(fd); + knServer_destroy(server); +} + +static uint8_t pattern(size_t offset) +{ + return (uint8_t)(offset % 251); +} + +/////////////////////////////////////////////////////////////////////////////// +/** + * @brief Send to [conn] until its out buffer is full (the client [fd] does + * not read), then check that everything arrives in order and that + * the server goes back to idle. + */ +/////////////////////////////////////////////////////////////////////////////// +static void flood_and_check(knServer *server, knConnection *conn, int fd) +{ + uint8_t chunk[1024]; + uint8_t *received; + size_t sent = 0; + int ret = KNEVTOK; + timestamp start; + + // NOTE: The client doesn't read, so the socket fills up, then the out buffer + while (sent < 64UL * 1024 * 1024) { + for (size_t i = 0; i < sizeof(chunk); ++i) { + chunk[i] = pattern(sent + i); + } + ret = knConnection_send(conn, chunk, sizeof(chunk)); + if (ret != KNEVTOK) + break; + sent += sizeof(chunk); + } + AssertEq(ret, KNEVTKICK, "Sending should eventually report a full out buffer"); + AssertEq(g_net.writes, 0, "onWrite should not be called before pollout"); + + received = malloc(sent); + AssertNotNull(received, "malloc should succeed"); + net_recvAll(server, fd, received, sent); + for (size_t i = 0; i < sent; ++i) { + AssertEq(received[i], pattern(i), "Byte %zu should arrive in order", i); + } + free(received); + NET_PUMP_UNTIL(server, g_net.writes >= 1); + + // NOTE: Once flushed, EPOLLOUT must be removed, or epoll_wait would not block + start = kl_monotonic(); + AssertEq(knServer_runOnce(server, 150), KNEVTOK, "runOnce should succeed"); + AssertGe(kl_monotonic() - start, (timestamp)100, "Server should be idle once the data is flushed"); + AssertEq(g_net.disconnects, 0, "Client should not have been kicked"); +} + +Test(tcp_server, pollout_flushes_in_order) +{ + knServer *server = net_server(42115, knTCP); + int fd = net_tcpClientBuf(42115, 4096); + + NET_PUMP_UNTIL(server, g_net.connects == 1); + flood_and_check(server, g_net.conns[0], fd); + close(fd); + knServer_destroy(server); +} + +Test(tcp_server, pollout_after_pool_growth) +{ + knServer *server = net_server(42117, knTCP); + int fd = net_tcpClientBuf(42117, 4096); + int others[10]; + + NET_PUMP_UNTIL(server, g_net.connects == 1); + // NOTE: More clients make the pool grow after the first one is registered + for (int i = 0; i < 10; ++i) { + others[i] = net_tcpClient(42117); + } + NET_PUMP_UNTIL(server, g_net.connects == 11); + flood_and_check(server, g_net.conns[0], fd); + for (int i = 0; i < 10; ++i) { + close(others[i]); + } + close(fd); + knServer_destroy(server); +} + +Test(tcp_server, destroy_closes_clients) +{ + knServer *server = net_server(42116, knTCP); + int fds[5]; + + for (int i = 0; i < 5; ++i) { + fds[i] = net_tcpClient(42116); + } + NET_PUMP_UNTIL(server, g_net.connects == 5); + knServer_destroy(server); + for (int i = 0; i < 5; ++i) { + Assert(net_isClosed(fds[i]), "Client %d should be closed when the server is destroyed", i); + close(fds[i]); + } +} diff --git a/tests/network/tests_udp_server.c b/tests/network/tests_udp_server.c new file mode 100644 index 0000000..274c8e0 --- /dev/null +++ b/tests/network/tests_udp_server.c @@ -0,0 +1,138 @@ +#include "net_utils.h" +#include + +Test(udp_server, create_destroy) +{ + knServer *server = knServer_create(42201, knUDP); + + AssertNotNull(server, "UDP server creation should succeed"); + Assert(knServer_isRunning(server), "Server should be running after creation"); + AssertEq(knServer_getPort(server), 42201, "Port should be the one given at creation"); + knServer_destroy(server); +} + +Test(udp_server, idle_runonce_waits_timeout) +{ + knServer *server = net_server(42202, knUDP); + timestamp start = kl_monotonic(); + + AssertEq(knServer_runOnce(server, 100), KNEVTOK, "Idle runOnce should succeed"); + AssertGe(kl_monotonic() - start, (timestamp)90, "Idle runOnce should block until its timeout"); + knServer_destroy(server); +} + +Test(udp_server, echo) +{ + knServer *server = net_server(42203, knUDP); + int fd = net_udpClient(); + char buf[64] = {0}; + + net_udpSend(fd, 42203, "ping", 4); + AssertEq(net_udpRecv(server, fd, buf, sizeof(buf)), (ssize_t)4, "Echo should be 4 bytes"); + AssertEq(memcmp(buf, "ping", 4), 0, "Echoed datagram should match"); + AssertEq(g_net.connects, 1, "First datagram should create a connection"); + AssertEq(g_net.reads, 1, "onRead should be called once"); + close(fd); + knServer_destroy(server); +} + +Test(udp_server, big_datagram_echo) +{ + knServer *server = net_server(42204, knUDP); + int fd = net_udpClient(); + uint8_t msg[4000]; + uint8_t buf[sizeof(msg)]; + + for (size_t i = 0; i < sizeof(msg); ++i) { + msg[i] = (uint8_t)(i * 7); + } + net_udpSend(fd, 42204, msg, sizeof(msg)); + AssertEq(net_udpRecv(server, fd, buf, sizeof(buf)), (ssize_t)sizeof(msg), "Whole datagram should be echoed"); + AssertEq(memcmp(buf, msg, sizeof(msg)), 0, "Echoed datagram should match"); + close(fd); + knServer_destroy(server); +} + +Test(udp_server, same_client_same_connection) +{ + knServer *server = net_server(42205, knUDP); + int fd = net_udpClient(); + char buf[16]; + + for (int i = 0; i < 3; ++i) { + net_udpSend(fd, 42205, "again", 5); + AssertEq(net_udpRecv(server, fd, buf, sizeof(buf)), (ssize_t)5, "Echo should be 5 bytes"); + } + AssertEq(g_net.reads, 3, "onRead should be called for each datagram"); + AssertEq(g_net.connects, 1, "A single client should be a single connection"); + close(fd); + knServer_destroy(server); +} + +Test(udp_server, many_clients_get_their_own_echo) +{ + knServer *server = net_server(42206, knUDP); + int fds[10]; + char msg[16]; + char buf[16]; + + for (int i = 0; i < 10; ++i) { + fds[i] = net_udpClient(); + snprintf(msg, sizeof(msg), "udp-%02d", i); + net_udpSend(fds[i], 42206, msg, 6); + } + for (int i = 0; i < 10; ++i) { + snprintf(msg, sizeof(msg), "udp-%02d", i); + AssertEq(net_udpRecv(server, fds[i], buf, sizeof(buf)), (ssize_t)6, "Echo should be 6 bytes"); + AssertEq(memcmp(buf, msg, 6), 0, "Client %d should receive its own echo", i); + } + AssertEq(g_net.connects, 10, "Each client should have its own connection"); + for (int i = 0; i < 10; ++i) { + close(fds[i]); + } + knServer_destroy(server); +} + +Test(udp_server, timeout_removes_clients) +{ + knServer *server = net_server(42207, knUDP); + timestamp start = kl_monotonic(); + int fds[3]; + char buf[16]; + + knServer_setConnectionTimeout(server, 200); + for (int i = 0; i < 3; ++i) { + fds[i] = net_udpClient(); + net_udpSend(fds[i], 42207, "hey", 3); + AssertEq(net_udpRecv(server, fds[i], buf, sizeof(buf)), (ssize_t)3, "Echo should be 3 bytes"); + } + NET_PUMP_UNTIL(server, g_net.disconnects == 3); + AssertGe(kl_monotonic() - start, (timestamp)200, "Clients should not be removed before the timeout"); + + // NOTE: A removed client talking again is a new connection + net_udpSend(fds[0], 42207, "back", 4); + AssertEq(net_udpRecv(server, fds[0], buf, sizeof(buf)), (ssize_t)4, "Echo should be 4 bytes"); + AssertEq(g_net.connects, 4, "A timed out client should get a new connection"); + for (int i = 0; i < 3; ++i) { + close(fds[i]); + } + knServer_destroy(server); +} + +Test(udp_server, active_client_is_not_removed) +{ + knServer *server = net_server(42208, knUDP); + int fd = net_udpClient(); + char buf[16]; + + knServer_setConnectionTimeout(server, 300); + for (int i = 0; i < 10; ++i) { + net_udpSend(fd, 42208, "ping", 4); + AssertEq(net_udpRecv(server, fd, buf, sizeof(buf)), (ssize_t)4, "Echo should be 4 bytes"); + net_pump(server, 100); + } + AssertEq(g_net.disconnects, 0, "An active client should never be removed"); + AssertEq(g_net.connects, 1, "An active client should keep its connection"); + close(fd); + knServer_destroy(server); +}