diff --git a/openair2/LAYER2/nr_rlc/nr_rlc_entity_am.c b/openair2/LAYER2/nr_rlc/nr_rlc_entity_am.c index d50f816072..480338fd84 100644 --- a/openair2/LAYER2/nr_rlc/nr_rlc_entity_am.c +++ b/openair2/LAYER2/nr_rlc/nr_rlc_entity_am.c @@ -767,8 +767,10 @@ void nr_rlc_entity_am_recv_pdu(nr_rlc_entity_t *_entity, nr_rlc_pdu_decoder_init(&decoder, buffer, size); dc = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder); - if (dc == 0) + if (dc == 0) { + LOG_D(RLC, "RLC received control PDU\n"); return process_control_pdu(entity, buffer, size); + } /* data PDU */ p = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder); @@ -839,6 +841,7 @@ void nr_rlc_entity_am_recv_pdu(nr_rlc_entity_t *_entity, entity->rx_list, pdu); /* do reception actions (38.322 5.2.3.2.3) */ + LOG_D(RLC, "RLC received PDU sn %d so %d is_first %d is_last %d data_size = %d \n", sn, so, is_first, is_last, data_size); reception_actions(entity, pdu); if (p) { @@ -852,15 +855,14 @@ void nr_rlc_entity_am_recv_pdu(nr_rlc_entity_t *_entity, entity->status_triggered = 1; if (!(sn_compare_rx(entity, sn, entity->rx_highest_status) < 0 || sn_compare_rx(entity, sn, v) >= 0)) { - LOG_D(RLC, "%s:%d:%s: warning: STATUS trigger should be delayed, according to specs\n", - __FILE__, __LINE__, __FUNCTION__); + LOG_D(RLC, "warning: STATUS triggerered but should be delayed according to specs\n"); } } return; err: - LOG_W(RLC, "%s:%d:%s: error decoding PDU, discarding\n", __FILE__, __LINE__, __FUNCTION__); + LOG_W(RLC, "RX error decoding PDU, discarding\n"); goto discard; discard: @@ -978,10 +980,7 @@ static nr_rlc_sdu_segment_t *resegment(nr_rlc_sdu_segment_t *sdu, pdu_header_size = compute_pdu_header_size(entity, sdu); next = calloc(1, sizeof(nr_rlc_sdu_segment_t)); - if (next == NULL) { - LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__); - exit(1); - } + AssertFatal(next != NULL, "out of memory\n"); *next = *sdu; over_size = pdu_header_size + sdu->size - pdu_size; @@ -994,7 +993,6 @@ static nr_rlc_sdu_segment_t *resegment(nr_rlc_sdu_segment_t *sdu, next->size = over_size; next->so = sdu->so + sdu->size; next->is_first = 0; - return next; } @@ -1594,6 +1592,8 @@ static int generate_retx_pdu(nr_rlc_entity_am_t *entity, char *buffer, entity->force_poll = 0; } + LOG_D(RLC, "RLC TX: sending sdu sn = %d is_first = %d, is_last = %d size = %d\n", sdu->sdu->sn, sdu->is_first, sdu->is_last, size); + int ret_size = serialize_sdu(entity, sdu, buffer, size, p); entity->common.stats.txpdu_pkts++; entity->common.stats.txpdu_bytes += ret_size; @@ -1612,11 +1612,14 @@ static int generate_tx_pdu(nr_rlc_entity_am_t *entity, char *buffer, int size) int p; /* sn out of window (that is: we have window stalling)? do nothing */ - if (is_window_stalling(entity)) + if (is_window_stalling(entity)) { + LOG_D(RLC, "Abort transmit due to window stall\n"); return 0; + } - if (entity->tx_list == NULL) + if (entity->tx_list == NULL) { return 0; + } sdu = entity->tx_list; @@ -1645,6 +1648,7 @@ static int generate_tx_pdu(nr_rlc_entity_am_t *entity, char *buffer, int size) /* segment if necessary */ if (pdu_size > size) { + LOG_D(RLC, "Segmentation (header %d + data %d) / (%d)\n", pdu_header_size, size - pdu_header_size, pdu_size - pdu_header_size); nr_rlc_sdu_segment_t *next_sdu; next_sdu = resegment(sdu, entity, size); /* put the second SDU back at the head of the TX list */ @@ -1693,7 +1697,7 @@ static int generate_tx_pdu(nr_rlc_entity_am_t *entity, char *buffer, int size) entity->common.stats.txpdu_pkts++; entity->common.stats.txpdu_bytes += ret_size; - /* No need to 'zero' time-of-arrival; + /* No need to 'zero' time-of-arrival; Segmented packets do need to be duplicated in time-sensitive use cases */ if (entity->common.avg_time_is_on) { uint64_t time_now = time_average_now(); @@ -1701,6 +1705,14 @@ static int generate_tx_pdu(nr_rlc_entity_am_t *entity, char *buffer, int size) time_average_add(entity->common.txsdu_avg_time_to_tx, time_now, waited_time); } + LOG_D(RLC, + "RLC TX: sending sdu sn = %d is_first = %d, is_last = %d header size = %d data size = %d\n", + sdu->sdu->sn, + sdu->is_first, + sdu->is_last, + pdu_header_size, + size - pdu_header_size); + return ret_size; // return serialize_sdu(entity, sdu, buffer, size, p); } @@ -1730,14 +1742,18 @@ int nr_rlc_entity_am_generate_pdu(nr_rlc_entity_t *_entity, if (status_to_report(entity)) { ret = generate_status(entity, buffer, size); - if (ret != 0) + if (ret != 0) { + LOG_D(RLC, "RLC transmit status pdu PDU\n"); return ret; + } } if (entity->retransmit_list != NULL) { ret = generate_retx_pdu(entity, buffer, size); - if (ret != 0) + if (ret != 0) { + LOG_D(RLC, "RLC retransmit PDU\n"); return ret; + } } return generate_tx_pdu(entity, buffer, size); @@ -1756,11 +1772,7 @@ void nr_rlc_entity_am_recv_sdu(nr_rlc_entity_t *_entity, entity->common.stats.rxsdu_pkts++; - if (size > NR_SDU_MAX) { - LOG_E(RLC, "%s:%d:%s: fatal: SDU size too big (%d bytes)\n", - __FILE__, __LINE__, __FUNCTION__, size); - exit(1); - } + AssertFatal(size <= NR_SDU_MAX, "Fatal: SDU size too big (%d bytes)\n", size); /* log SDUs rejected, at most once per second */ if (entity->sdu_rejected != 0 diff --git a/openair2/LAYER2/nr_rlc/tests/CMakeLists.txt b/openair2/LAYER2/nr_rlc/tests/CMakeLists.txt index 1f616e7927..c8d365ddf9 100644 --- a/openair2/LAYER2/nr_rlc/tests/CMakeLists.txt +++ b/openair2/LAYER2/nr_rlc/tests/CMakeLists.txt @@ -12,3 +12,13 @@ add_test(NAME nr_rlc_tests COMMAND exec_nr_rlc_test.sh ${CMAKE_CURRENT_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +add_executable(test_nr_rlc_am_entity test_nr_rlc_am_entity.cpp) +target_link_libraries(test_nr_rlc_am_entity PRIVATE nr_rlc_core GTest::gtest minimal_lib) +add_dependencies(tests test_nr_rlc_am_entity) +add_test(NAME test_nr_rlc_am_entity COMMAND ./test_nr_rlc_am_entity) + +add_executable(benchmark_nr_rlc_am_entity benchmark_nr_rlc_am_entity.cpp) +target_link_libraries(benchmark_nr_rlc_am_entity PRIVATE benchmark::benchmark nr_rlc_core UTIL ${T_LIB} minimal_lib) +add_dependencies(tests benchmark_nr_rlc_am_entity) +add_test(NAME benchmark_nr_rlc_am_entity + COMMAND ./benchmark_nr_rlc_am_entity) diff --git a/openair2/LAYER2/nr_rlc/tests/benchmark_nr_rlc_am_entity.cpp b/openair2/LAYER2/nr_rlc/tests/benchmark_nr_rlc_am_entity.cpp new file mode 100644 index 0000000000..ac36b79211 --- /dev/null +++ b/openair2/LAYER2/nr_rlc/tests/benchmark_nr_rlc_am_entity.cpp @@ -0,0 +1,112 @@ +/* + * 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 "benchmark/benchmark.h" +extern "C" { +#include "openair2/LAYER2/nr_rlc/nr_rlc_entity_am.h" +#include "openair2/LAYER2/nr_rlc/nr_rlc_entity.h" +#include "common/utils/LOG/log.h" +#include "common/config/config_load_configmodule.h" +extern configmodule_interface_t *uniqCfg; +} +#include + +void deliver_sdu(void *deliver_sdu_data, struct nr_rlc_entity_t *entity, char *buf, int size) +{ +} +void sdu_successful_delivery(void *sdu_successful_delivery_data, struct nr_rlc_entity_t *entity, int sdu_id) +{ +} +void max_retx_reached(void *max_retx_reached_data, struct nr_rlc_entity_t *entity) +{ +} + +static void BM_nr_rlc_am_entity(benchmark::State &state) +{ + nr_rlc_entity_t *tx_entity = new_nr_rlc_entity_am(10000, + 10000, + deliver_sdu, + NULL, + sdu_successful_delivery, + NULL, + max_retx_reached, + NULL, + 5, + 35, + 0, + 4, + -1, + 8, + 12); + + nr_rlc_entity_t *rx_entity = new_nr_rlc_entity_am(10000, + 10000, + deliver_sdu, + NULL, + sdu_successful_delivery, + NULL, + max_retx_reached, + NULL, + 5, + 35, + 0, + 4, + -1, + 8, + 12); + + char message[] = "Message to the other RLC AM entity."; + for (auto _ : state) { + char pdu_buf[40]; + for (int i = 0; i < state.range(0); i++) { + tx_entity->recv_sdu(tx_entity, message, sizeof(message), i); + int size = tx_entity->generate_pdu(tx_entity, pdu_buf, sizeof(pdu_buf)); + if (size != 0) { + // 10% packet loss + if (i % 10 != 0) + rx_entity->recv_pdu(rx_entity, pdu_buf, size); + } + // Allow the lists to build up before sending messages in the other direction + if (i % 100 == 0) { + size = rx_entity->generate_pdu(rx_entity, pdu_buf, sizeof(pdu_buf)); + if (size != 0) { + // No packet lost in the other direction + tx_entity->recv_pdu(tx_entity, pdu_buf, size); + } + } + tx_entity->set_time(tx_entity, i + 1); + rx_entity->set_time(tx_entity, i + 1); + } + } + tx_entity->delete_entity(tx_entity); + rx_entity->delete_entity(rx_entity); +} + +BENCHMARK(BM_nr_rlc_am_entity)->RangeMultiplier(4)->Range(100, 20000); + +int main(int argc, char **argv) +{ + logInit(); + uniqCfg = load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY); + g_log->log_component[RLC].level = -1; + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + return 0; +} diff --git a/openair2/LAYER2/nr_rlc/tests/test_nr_rlc_am_entity.cpp b/openair2/LAYER2/nr_rlc/tests/test_nr_rlc_am_entity.cpp new file mode 100644 index 0000000000..5407f7b2ae --- /dev/null +++ b/openair2/LAYER2/nr_rlc/tests/test_nr_rlc_am_entity.cpp @@ -0,0 +1,226 @@ +/* + * 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 "gtest/gtest.h" +extern "C" { +#include "openair2/LAYER2/nr_rlc/nr_rlc_entity_am.h" +#include "openair2/LAYER2/nr_rlc/nr_rlc_entity.h" +#include "common/utils/LOG/log.h" +#include "common/config/config_load_configmodule.h" +extern configmodule_interface_t *uniqCfg; +} +#include + +int sdu_delivered_count = 0; +int sdu_acked_count = 0; +void deliver_sdu(void *deliver_sdu_data, struct nr_rlc_entity_t *entity, char *buf, int size) +{ + sdu_delivered_count++; + char payload[300]; + ASSERT_LE(size, sizeof(payload)); + snprintf(payload, size, "%s", buf); + std::cout << "Delivered sdu: " << payload << std::endl; +} + +void sdu_successful_delivery(void *sdu_successful_delivery_data, struct nr_rlc_entity_t *entity, int sdu_id) +{ + sdu_acked_count++; + std::cout << "SDU " << sdu_id << " acked" << std::endl; +} + +void max_retx_reached(void *max_retx_reached_data, struct nr_rlc_entity_t *entity) +{ +} + +TEST(nr_rlc_am_entity, test_init) +{ + nr_rlc_entity_t *entity = new_nr_rlc_entity_am(100, + 100, + deliver_sdu, + NULL, + sdu_successful_delivery, + NULL, + max_retx_reached, + NULL, + 45, + 35, + 0, + -1, + -1, + 8, + 12); + char buf[30]; + EXPECT_EQ(entity->generate_pdu(entity, buf, sizeof(buf)), 0) << "No PDCP SDU provided to RLC, expected no RLC PDUS"; + + entity->delete_entity(entity); +} + +TEST(nr_rlc_am_entity, test_segmentation_reassembly) +{ + nr_rlc_entity_t *tx_entity = new_nr_rlc_entity_am(100, + 100, + deliver_sdu, + NULL, + sdu_successful_delivery, + NULL, + max_retx_reached, + NULL, + 45, + 35, + 0, + -1, + -1, + 8, + 12); + + nr_rlc_entity_t *rx_entity = new_nr_rlc_entity_am(100, + 100, + deliver_sdu, + NULL, + sdu_successful_delivery, + NULL, + max_retx_reached, + NULL, + 45, + 35, + 0, + -1, + -1, + 8, + 12); + char buf[30] = {0}; + snprintf(buf, sizeof(buf), "%s", "Message"); + EXPECT_EQ(tx_entity->generate_pdu(tx_entity, buf, sizeof(buf)), 0) << "No PDCP SDU provided to RLC, expected no RLC PDUS"; + + // Higher layer IF -> deliver PDCP SDU + tx_entity->recv_sdu(tx_entity, buf, sizeof(buf), 0); + sdu_delivered_count = 0; + for (auto i = 0; i < 10; i++) { + if (sdu_delivered_count == 1) { + break; + } + int size = tx_entity->generate_pdu(tx_entity, buf, 10); + EXPECT_GT(size, 0); + rx_entity->recv_pdu(rx_entity, buf, size); + } + EXPECT_EQ(sdu_delivered_count, 1); + sdu_acked_count = 0; + int size = rx_entity->generate_pdu(rx_entity, buf, 30); + tx_entity->recv_pdu(tx_entity, buf, size); + EXPECT_EQ(sdu_acked_count, 1); + + tx_entity->delete_entity(tx_entity); + rx_entity->delete_entity(rx_entity); +} + +TEST(nr_rlc_am_entity, test_ack_out_of_order) +{ + nr_rlc_entity_t *tx_entity = new_nr_rlc_entity_am(100, + 100, + deliver_sdu, + NULL, + sdu_successful_delivery, + NULL, + max_retx_reached, + NULL, + 5, + 35, + 0, + 4, + -1, + 8, + 12); + + nr_rlc_entity_t *rx_entity = new_nr_rlc_entity_am(100, + 100, + deliver_sdu, + NULL, + sdu_successful_delivery, + NULL, + max_retx_reached, + NULL, + 5, + 35, + 0, + 4, + -1, + 8, + 12); + + char buf[30] = {0}; + EXPECT_EQ(tx_entity->generate_pdu(tx_entity, buf, sizeof(buf)), 0) << "No PDCP SDU provided to RLC, expected no RLC PDUS"; + // Higher layer IF -> deliver PDCP SDU + // Generate 4 PDCP SDUS + snprintf(buf, sizeof(buf), "%s", "Message 1"); + tx_entity->recv_sdu(tx_entity, buf, sizeof(buf), 0); + snprintf(buf, sizeof(buf), "%s", "Message 2"); + tx_entity->recv_sdu(tx_entity, buf, sizeof(buf), 1); + snprintf(buf, sizeof(buf), "%s", "Message 3"); + tx_entity->recv_sdu(tx_entity, buf, sizeof(buf), 2); + + sdu_delivered_count = 0; + sdu_acked_count = 0; + char pdu_buf[40]; + uint64_t time = 0; + for (auto i = 0; i < 3; i++) { + int size = tx_entity->generate_pdu(tx_entity, pdu_buf, sizeof(pdu_buf)); + EXPECT_GT(size, 0); + // Do not deliver PDU 2 to RX entity + if (i != 1) { + rx_entity->recv_pdu(rx_entity, pdu_buf, size); + } + tx_entity->set_time(tx_entity, time); + rx_entity->set_time(tx_entity, time); + time++; + } + EXPECT_EQ(sdu_delivered_count, 2) << "Expected 2 out of 3 SDUs delivered"; + + int size = rx_entity->generate_pdu(rx_entity, pdu_buf, sizeof(pdu_buf)); + EXPECT_GT(size, 0); + tx_entity->recv_pdu(tx_entity, pdu_buf, size); + + // Triggers t-poll-retransmit and retransmission of the lost PDU + for (auto i = 0; i < 5; i++) { + tx_entity->set_time(tx_entity, time); + rx_entity->set_time(tx_entity, time); + size = tx_entity->generate_pdu(tx_entity, pdu_buf, sizeof(pdu_buf)); + if (size != 0) + rx_entity->recv_pdu(rx_entity, pdu_buf, size); + size = rx_entity->generate_pdu(rx_entity, pdu_buf, sizeof(pdu_buf)); + if (size != 0) + tx_entity->recv_pdu(tx_entity, pdu_buf, size); + time++; + } + EXPECT_EQ(sdu_acked_count, 3); + EXPECT_EQ(sdu_delivered_count, 3); + + tx_entity->delete_entity(tx_entity); + rx_entity->delete_entity(rx_entity); +} + +int main(int argc, char **argv) +{ + logInit(); + uniqCfg = load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY); + g_log->log_component[RLC].level = OAILOG_TRACE; + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}