From e7f858f6b2c02579f7e9229131e8057acfee600d Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Mon, 30 Sep 2024 20:50:57 +0200 Subject: [PATCH 1/2] Add iterator to hashtable and hashtable unit tests --- common/utils/CMakeLists.txt | 3 + common/utils/hashtable/hashtable.c | 21 ++++ common/utils/hashtable/hashtable.h | 11 +- common/utils/hashtable/tests/CMakeLists.txt | 5 + .../utils/hashtable/tests/test_hashtable.cpp | 106 ++++++++++++++++++ 5 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 common/utils/hashtable/tests/CMakeLists.txt create mode 100644 common/utils/hashtable/tests/test_hashtable.cpp diff --git a/common/utils/CMakeLists.txt b/common/utils/CMakeLists.txt index 025091249b..7cf067dd14 100644 --- a/common/utils/CMakeLists.txt +++ b/common/utils/CMakeLists.txt @@ -16,3 +16,6 @@ add_subdirectory(threadPool) add_library(utils utils.c system.c time_meas.c time_stat.c tun_if.c) target_include_directories(utils PUBLIC .) target_link_libraries(utils PRIVATE ${T_LIB}) +if (ENABLE_TESTS) + add_subdirectory(hashtable/tests) +endif() diff --git a/common/utils/hashtable/hashtable.c b/common/utils/hashtable/hashtable.c index e9641f8e0c..bbd03692e4 100644 --- a/common/utils/hashtable/hashtable.c +++ b/common/utils/hashtable/hashtable.c @@ -305,3 +305,24 @@ hashtable_rc_t hashtable_get(const hash_table_t *const hashtblP, const hash_key_ *dataP = NULL; return HASH_TABLE_KEY_NOT_EXISTS; } + +hash_table_iterator_s hashtable_get_iterator(const hash_table_t *const hashtbl) +{ + return (hash_table_iterator_s){.index = 0, .node = hashtbl->nodes[0], .hashtbl = hashtbl}; +} + +bool hashtable_iterator_getnext(hash_table_iterator_s *iterator, void **dataP) +{ + // Iterate over table indexes + while (iterator->node == NULL) { + iterator->index++; + if (iterator->index >= iterator->hashtbl->size) { + *dataP = NULL; + return false; + } + iterator->node = iterator->hashtbl->nodes[iterator->index]; + } + *dataP = iterator->node->data; + iterator->node = iterator->node->next; + return true; +} diff --git a/common/utils/hashtable/hashtable.h b/common/utils/hashtable/hashtable.h index 09f8623ce2..c50c23ae7b 100644 --- a/common/utils/hashtable/hashtable.h +++ b/common/utils/hashtable/hashtable.h @@ -24,6 +24,7 @@ #include #include #include +#include typedef size_t hash_size_t; typedef uint64_t hash_key_t; @@ -54,6 +55,12 @@ typedef struct hash_table_s { void (*freefunc)(void *); } hash_table_t; +typedef struct hash_table_iterator_s { + int index; + hash_node_t* node; + const hash_table_t* const hashtbl; +} hash_table_iterator_s; + char *hashtable_rc_code2string(hashtable_rc_t rcP); void hash_free_int_func(void *memoryP); hash_table_t *hashtable_create (const hash_size_t size, hash_size_t (*hashfunc)(const hash_key_t ), void (*freefunc)(void *)); @@ -63,8 +70,8 @@ hashtable_rc_t hashtable_dump_content (const hash_table_t *const hashtblP, char hashtable_rc_t hashtable_insert (hash_table_t *const hashtbl, const hash_key_t key, void *data); hashtable_rc_t hashtable_remove (hash_table_t *const hashtbl, const hash_key_t key); hashtable_rc_t hashtable_get (const hash_table_t *const hashtbl, const hash_key_t key, void **dataP); - - +hash_table_iterator_s hashtable_get_iterator(const hash_table_t *const hashtbl); +bool hashtable_iterator_getnext(hash_table_iterator_s* iterator, void **dataP); #endif diff --git a/common/utils/hashtable/tests/CMakeLists.txt b/common/utils/hashtable/tests/CMakeLists.txt new file mode 100644 index 0000000000..baf2461262 --- /dev/null +++ b/common/utils/hashtable/tests/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(test_hashtable test_hashtable.cpp) +add_dependencies(tests test_hashtable) +target_link_libraries(test_hashtable PRIVATE HASHTABLE GTest::gtest) +add_test(NAME test_hashtable + COMMAND ./test_hashtable) diff --git a/common/utils/hashtable/tests/test_hashtable.cpp b/common/utils/hashtable/tests/test_hashtable.cpp new file mode 100644 index 0000000000..aa63caa331 --- /dev/null +++ b/common/utils/hashtable/tests/test_hashtable.cpp @@ -0,0 +1,106 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include +extern "C" { +#include "hashtable.h" +} + +TEST(hashtable, test_insert_remove) { + hash_table_t* table = hashtable_create(250, NULL, NULL); + int* a = (int*)calloc(1, sizeof(*a)); + EXPECT_EQ(hashtable_insert(table, 1, a), HASH_TABLE_OK); + EXPECT_EQ(hashtable_remove(table, 1), HASH_TABLE_OK); + EXPECT_EQ(hashtable_destroy(&table), HASH_TABLE_OK); +} + +TEST(hashtable, test_insert_iterate_remove) { + hash_table_t* table = hashtable_create(250, NULL, NULL); + int* a = (int*)calloc(1, sizeof(*a)); + EXPECT_EQ(hashtable_insert(table, 1, a), HASH_TABLE_OK); + auto iterator = hashtable_get_iterator(table); + void* ptr; + int num_items = 0; + while(hashtable_iterator_getnext(&iterator, &ptr)) { + num_items++; + } + EXPECT_EQ(num_items, 1); + EXPECT_EQ(hashtable_remove(table, 1), HASH_TABLE_OK); + num_items = 0; + + auto iterator2 = hashtable_get_iterator(table); + while(hashtable_iterator_getnext(&iterator2, &ptr)) { + num_items++; + } + EXPECT_EQ(num_items, 0); + EXPECT_EQ(hashtable_destroy(&table), HASH_TABLE_OK); +} + +TEST(hashtable, test_insert_three_iterate_remove) { + hash_table_t* table = hashtable_create(250, NULL, NULL); + int* a1 = (int*)calloc(1, sizeof(*a1)); + int* a2 = (int*)calloc(1, sizeof(*a2)); + int* a3 = (int*)calloc(1, sizeof(*a3)); + EXPECT_EQ(hashtable_insert(table, 1, a1), HASH_TABLE_OK); + EXPECT_EQ(hashtable_insert(table, 2, a2), HASH_TABLE_OK); + EXPECT_EQ(hashtable_insert(table, 3, a3), HASH_TABLE_OK); + auto iterator = hashtable_get_iterator(table); + void* ptr; + int num_items = 0; + while(hashtable_iterator_getnext(&iterator, &ptr)) { + num_items++; + } + EXPECT_EQ(num_items, 3); + EXPECT_EQ(hashtable_remove(table, 1), HASH_TABLE_OK); + num_items = 0; + + auto iterator2 = hashtable_get_iterator(table); + while(hashtable_iterator_getnext(&iterator2, &ptr)) { + num_items++; + } + EXPECT_EQ(num_items, 2); + EXPECT_EQ(hashtable_destroy(&table), HASH_TABLE_OK); +} + +TEST(hashtable, test_insert_empty_insert_interate) { + hash_table_t* table = hashtable_create(250, NULL, NULL); + int* a = (int*)calloc(1, sizeof(*a)); + EXPECT_EQ(hashtable_insert(table, 1, a), HASH_TABLE_OK); + EXPECT_EQ(hashtable_remove(table, 1), HASH_TABLE_OK); + + int* a2 = (int*)calloc(1, sizeof(*a2)); + EXPECT_EQ(hashtable_insert(table, 1, a2), HASH_TABLE_OK); + auto iterator3 = hashtable_get_iterator(table); + int num_items = 0; + void* ptr; + while(hashtable_iterator_getnext(&iterator3, &ptr)) { + num_items++; + } + EXPECT_EQ(num_items, 1); + + EXPECT_EQ(hashtable_destroy(&table), HASH_TABLE_OK); +} + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From 883333bfe634bde5f32052a32921334a84e56bf4 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Fri, 11 Oct 2024 10:57:10 +0200 Subject: [PATCH 2/2] Use hashtable for mapping FDs to buffer_ts in rfsimulator. Use hashtable for determining which buffer_t does a sock map to. This lifts the restriction for number of FDs used by applications using rfsimulator. --- radio/rfsimulator/CMakeLists.txt | 2 +- radio/rfsimulator/simulator.c | 73 ++++++++++++++++++++++++++------ 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/radio/rfsimulator/CMakeLists.txt b/radio/rfsimulator/CMakeLists.txt index efba710bad..61b053a1ce 100644 --- a/radio/rfsimulator/CMakeLists.txt +++ b/radio/rfsimulator/CMakeLists.txt @@ -3,7 +3,7 @@ add_library(rfsimulator MODULE apply_channelmod.c ../../openair1/PHY/TOOLS/signal_energy.c ) -target_link_libraries(rfsimulator PRIVATE SIMU) +target_link_libraries(rfsimulator PRIVATE SIMU HASHTABLE) set_target_properties(rfsimulator PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_executable(replay_node stored_node.c) diff --git a/radio/rfsimulator/simulator.c b/radio/rfsimulator/simulator.c index 3c19b492c8..d362d5ef7b 100644 --- a/radio/rfsimulator/simulator.c +++ b/radio/rfsimulator/simulator.c @@ -50,6 +50,7 @@ #define CHANNELMOD_DYNAMICLOAD #include #include "rfsimulator.h" +#include "hashtable.h" #define PORT 4043 //default TCP port for this simulator // @@ -82,6 +83,8 @@ // Until a convenient management is implemented, the MAX_FD_RFSIMU is used everywhere (instead of // FD_SETSIE) and reduced to 125. This should allow for around 20 simultaeous UEs. // +// An indirection level via hashtable was added to allow the software to use FDs above MAX_FD_RFSIMU. +// // #define MAX_FD_RFSIMU FD_SETSIZE #define MAX_FD_RFSIMU 250 #define SEND_BUFF_SIZE 100000000 // Socket buffer size @@ -162,6 +165,9 @@ typedef struct { uint16_t port; int saveIQfile; buffer_t buf[MAX_FD_RFSIMU]; + int next_buf; + // Hashtable used as an indirection level between file descriptor and the buf array + hash_table_t *fd_to_buf_map; int rx_num_channels; int tx_num_channels; double sample_rate; @@ -178,11 +184,38 @@ typedef struct { double prop_delay_ms; } rfsimulator_state_t; + +static buffer_t *get_buff_from_socket(rfsimulator_state_t *simulator_state, int socket) +{ + uint64_t buffer_index; + if (hashtable_get(simulator_state->fd_to_buf_map, socket, (void **)&buffer_index) == HASH_TABLE_OK) { + return &simulator_state->buf[buffer_index]; + } else { + return NULL; + } +} + +static void add_buff_to_socket_mapping(rfsimulator_state_t *simulator_state, int socket, uint64_t buff_index) +{ + hashtable_rc_t rc = hashtable_insert(simulator_state->fd_to_buf_map, socket, (void *)buff_index); + AssertFatal(rc == HASH_TABLE_OK, + "%s sock = %d\n", + rc == HASH_TABLE_INSERT_OVERWRITTEN_DATA ? "Duplicate entry in hashtable" : "Hashtable is not allocated", + socket); +} + +static void remove_buff_to_socket_mapping(rfsimulator_state_t *simulator_state, int socket) +{ + // Failure is fine here + hashtable_remove(simulator_state->fd_to_buf_map, socket); +} + static int allocCirBuf(rfsimulator_state_t *bridge, int sock) { /* TODO: cleanup code so that this AssertFatal becomes useless */ AssertFatal(sock >= 0 && sock < sizeofArray(bridge->buf), "socket %d is not in range\n", sock); - buffer_t *ptr=&bridge->buf[sock]; + uint64_t buff_index = bridge->next_buf++ % MAX_FD_RFSIMU; + buffer_t *ptr=&bridge->buf[buff_index]; ptr->circularBuf = calloc(1, sampleToByte(CirSize, 1)); if (ptr->circularBuf == NULL) { LOG_E(HW, "malloc(%lu) failed\n", sampleToByte(CirSize, 1)); @@ -235,6 +268,7 @@ static int allocCirBuf(rfsimulator_state_t *bridge, int sock) random_channel(ptr->channel_model,false); LOG_I(HW, "Random channel %s in rfsimulator activated\n", modelname); } + add_buff_to_socket_mapping(bridge, sock, buff_index); return 0; } @@ -243,17 +277,23 @@ static void removeCirBuf(rfsimulator_state_t *bridge, int sock) { LOG_E(HW, "epoll_ctl(EPOLL_CTL_DEL) failed\n"); } close(sock); - free(bridge->buf[sock].circularBuf); - // Fixme: no free_channel_desc_scm(bridge->buf[sock].channel_model) implemented - // a lot of mem leaks - //free(bridge->buf[sock].channel_model); - memset(&bridge->buf[sock], 0, sizeof(buffer_t)); - bridge->buf[sock].conn_sock=-1; - nb_ue--; + buffer_t* buf = get_buff_from_socket(bridge, sock); + if (buf) { + free(buf->circularBuf); + // Fixme: no free_channel_desc_scm(bridge->buf[sock].channel_model) implemented + // a lot of mem leaks + //free(bridge->buf[sock].channel_model); + memset(buf, 0, sizeof(buffer_t)); + buf->conn_sock=-1; + remove_buff_to_socket_mapping(bridge, sock); + nb_ue--; + } } static void socketError(rfsimulator_state_t *bridge, int sock) { - if (bridge->buf[sock].conn_sock!=-1) { + buffer_t* buf = get_buff_from_socket(bridge, sock); + if (!buf) return; + if (buf->conn_sock != -1) { LOG_W(HW, "Lost socket\n"); removeCirBuf(bridge, sock); @@ -789,7 +829,7 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, int nsamps_for_initi rfsimulator_write_internal(t, t->lastWroteTS > 1 ? t->lastWroteTS - 1 : 0, samplesVoid, 1, t->tx_num_channels, 1, false); - buffer_t *b = &t->buf[conn_sock]; + buffer_t *b = get_buff_from_socket(t, conn_sock); if (b->channel_model) b->channel_model->start_TS = t->lastWroteTS; } else { @@ -798,8 +838,8 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, int nsamps_for_initi continue; } - buffer_t *b=&t->buf[fd]; - + buffer_t *b = get_buff_from_socket(t, fd); + if (!b) continue; if ( b->circularBuf == NULL ) { LOG_E(HW, "Received data on not connected socket %d\n", events[nbEv].data.fd); continue; @@ -1044,6 +1084,7 @@ static void rfsimulator_end(openair0_device *device) { removeCirBuf(s, b->conn_sock); } close(s->epollfd); + hashtable_destroy(&s->fd_to_buf_map); } static int rfsimulator_stop(openair0_device *device) { return 0; @@ -1059,6 +1100,12 @@ static int rfsimulator_set_gains(openair0_device *device, openair0_config_t *ope static int rfsimulator_write_init(openair0_device *device) { return 0; } + +void do_not_free_integer(void *integer) +{ + (void)integer; +} + __attribute__((__visibility__("default"))) int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { // to change the log level, use this on command line @@ -1104,6 +1151,8 @@ int device_init(openair0_device *device, openair0_config_t *openair0_cfg) { for (int i = 0; i < MAX_FD_RFSIMU; i++) rfsimulator->buf[i].conn_sock=-1; + rfsimulator->next_buf = 0; + rfsimulator->fd_to_buf_map = hashtable_create(MAX_FD_RFSIMU, NULL, do_not_free_integer); AssertFatal((rfsimulator->epollfd = epoll_create1(0)) != -1, "epoll_create1() failed, errno(%d)", errno); // we need to call randominit() for telnet server (use gaussdouble=>uniformrand)