diff --git a/lib/include/srsran/rlc/rlc_am_lte.h b/lib/include/srsran/rlc/rlc_am_lte.h index ec20c0082..5710a07c5 100644 --- a/lib/include/srsran/rlc/rlc_am_lte.h +++ b/lib/include/srsran/rlc/rlc_am_lte.h @@ -86,6 +86,7 @@ private: void retransmit_pdu(uint32_t sn); // Helpers + bool window_full(); bool poll_required(); bool do_status(); void check_sn_reached_max_retx(uint32_t sn); diff --git a/lib/src/rlc/rlc_am_lte.cc b/lib/src/rlc/rlc_am_lte.cc index 8279a8858..f010a6860 100644 --- a/lib/src/rlc/rlc_am_lte.cc +++ b/lib/src/rlc/rlc_am_lte.cc @@ -242,7 +242,7 @@ void rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, uint32_t& n } // Bytes needed for tx SDUs - if (tx_window.size() < 1024) { + if (not window_full()) { n_sdus = tx_sdu_queue.get_n_sdus(); n_bytes_newtx += tx_sdu_queue.size_bytes(); if (tx_sdu != NULL) { @@ -290,7 +290,7 @@ uint32_t rlc_am_lte_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) } // Section 5.2.2.3 in TS 36.311, if tx_window is full and retx_queue empty, retransmit PDU - if (tx_window.size() >= RLC_AM_WINDOW_SIZE && retx_queue.empty()) { + if (window_full() && retx_queue.empty()) { retransmit_pdu(vt_a); } @@ -314,7 +314,7 @@ void rlc_am_lte_tx::timer_expired(uint32_t timeout_id) // Section 5.2.2.3 in TS 36.322, schedule PDU for retransmission if // (a) both tx and retx buffer are empty (excluding tx'ed PDU waiting for ack), or // (b) no new data PDU can be transmitted (tx window is full) - if ((retx_queue.empty() && tx_sdu_queue.size() == 0) || tx_window.size() >= RLC_AM_WINDOW_SIZE) { + if ((retx_queue.empty() && tx_sdu_queue.size() == 0) || window_full()) { retransmit_pdu(vt_a); // TODO: TS says to send vt_s - 1 here } } else if (status_prohibit_timer.is_valid() && status_prohibit_timer.id() == timeout_id) { @@ -359,6 +359,14 @@ void rlc_am_lte_tx::retransmit_pdu(uint32_t sn) * Helper functions ***************************************************************************/ +bool rlc_am_lte_tx::window_full() +{ + if ((vt_s - vt_a) >= RLC_AM_WINDOW_SIZE) { + return true; + } + return false; +}; + /** * Called when building a RLC PDU for checking whether the poll bit needs * to be set. @@ -385,7 +393,7 @@ bool rlc_am_lte_tx::poll_required() return true; } - if (tx_window.size() >= RLC_AM_WINDOW_SIZE) { + if (window_full()) { RlcDebug("Poll required. Cause: TX window full."); return true; } @@ -690,7 +698,7 @@ int rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_bytes) } // do not build any more PDU if window is already full - if (tx_window.size() >= RLC_AM_WINDOW_SIZE) { + if (window_full()) { RlcInfo("Cannot build data PDU - Tx window full."); return 0; } @@ -1156,7 +1164,8 @@ rlc_am_lte_rx::rlc_am_lte_rx(rlc_am* parent_) : pool(byte_buffer_pool::get_instance()), reordering_timer(parent_->timers->get_unique_timer()), rlc_am_base_rx(parent_, parent_->logger) -{} +{ +} bool rlc_am_lte_rx::configure(const rlc_config_t& cfg_) { diff --git a/lib/test/rlc/rlc_am_lte_test.cc b/lib/test/rlc/rlc_am_lte_test.cc index 984d6afdd..e3d94459e 100644 --- a/lib/test/rlc/rlc_am_lte_test.cc +++ b/lib/test/rlc/rlc_am_lte_test.cc @@ -3757,6 +3757,176 @@ bool poll_retx_expiry_test() return SRSRAN_SUCCESS; } +bool full_window_check_test() +{ + rlc_config_t config = rlc_config_t::default_rlc_am_config(); + // [I] SRB1 configured: t_poll_retx=65, poll_pdu=-1, poll_byte=-1, max_retx_thresh=6, t_reordering=55, + // t_status_prohibit=0 + config.am.t_poll_retx = 65; + config.am.poll_pdu = -1; + config.am.poll_byte = -1; + config.am.max_retx_thresh = 6; + config.am.t_reordering = 55; + config.am.t_status_prohibit = 55; + +#if HAVE_PCAP + rlc_pcap pcap; + pcap.open("rlc_am_poll_rext_expiry_test.pcap", config); + rlc_am_tester tester(true, &pcap); +#else + rlc_am_tester tester(true, NULL); +#endif + + srsran::timer_handler timers(8); + + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + + srslog::fetch_basic_logger("RLC_AM_1").set_hex_dump_max_size(100); + srslog::fetch_basic_logger("RLC_AM_2").set_hex_dump_max_size(100); + srslog::fetch_basic_logger("RLC").set_hex_dump_max_size(100); + + if (not rlc1.configure(config)) { + return -1; + } + + if (not rlc2.configure(config)) { + return -1; + } + + { + // Initial Tx + uint32_t num_tx_pdus = 512; + for (uint32_t i = 0; i < num_tx_pdus; ++i) { + // Write SDU + unique_byte_buffer_t sdu = srsran::make_byte_buffer(); + TESTASSERT(sdu != nullptr); + sdu->N_bytes = 1; + sdu->msg[0] = i; + sdu->md.pdcp_sn = i; + rlc1.write_sdu(std::move(sdu)); + + unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + TESTASSERT(pdu != nullptr); + pdu->N_bytes = 1; + pdu->msg[0] = i; + pdu->md.pdcp_sn = i; + pdu->N_bytes = rlc1.read_pdu(pdu->msg, 3); + TESTASSERT(pdu->N_bytes == 3); + } + } + { + // Tx one more to check the window is full + unique_byte_buffer_t sdu = srsran::make_byte_buffer(); + TESTASSERT(sdu != nullptr); + sdu->N_bytes = 1; + sdu->msg[0] = 0; + sdu->md.pdcp_sn = 512; + rlc1.write_sdu(std::move(sdu)); + + unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + TESTASSERT(pdu != nullptr); + pdu->N_bytes = rlc1.read_pdu(pdu->msg, 3); + TESTASSERT(pdu->N_bytes == 3); + + // If the TX window is full, we should RETX SN=0 + rlc_amd_pdu_header_t header = {}; + rlc_am_read_data_pdu_header(&pdu->msg, &pdu->N_bytes, &header); + TESTASSERT_EQ(header.sn, 0); + TESTASSERT_EQ(header.N_li, 0); + TESTASSERT_EQ(header.fi, 0); + } + + // Ack one SN in the middle of the TX window. + // This is done to make sure the full window check is correct + // even if PDUs in the middle of the window are ACKed. + // ACK_SN=3, NACK_SN=0 + { + rlc_status_pdu_t status = {}; + status.ack_sn = 3; + status.N_nack = 1; + status.nacks[0].nack_sn = 0; + + unique_byte_buffer_t status_buf = srsran::make_byte_buffer(); + TESTASSERT(status_buf != nullptr); + rlc_am_write_status_pdu(&status, status_buf.get()); + rlc1.write_pdu(status_buf->msg, status_buf->N_bytes); + + // Read RETX for SN=0 from NACK + unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + TESTASSERT(pdu != nullptr); + pdu->N_bytes = rlc1.read_pdu(pdu->msg, 3); + TESTASSERT(pdu->N_bytes == 3); + + // Check RETX SN=0 + rlc_amd_pdu_header_t header = {}; + rlc_am_read_data_pdu_header(&pdu->msg, &pdu->N_bytes, &header); + TESTASSERT_EQ(header.sn, 0); + TESTASSERT_EQ(header.N_li, 0); + TESTASSERT_EQ(header.fi, 0); + TESTASSERT_EQ(0, rlc1.get_buffer_state()); + } + { + // Tx more PDUs to check the window is still full + uint32_t num_tx_pdus = 2; + for (uint32_t i = 0; i < num_tx_pdus; ++i) { + // Write SDU + unique_byte_buffer_t sdu = srsran::make_byte_buffer(); + TESTASSERT(sdu != nullptr); + sdu->N_bytes = 1; + sdu->msg[0] = i; + sdu->md.pdcp_sn = i; + rlc1.write_sdu(std::move(sdu)); + + unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + TESTASSERT(pdu != nullptr); + pdu->N_bytes = 1; + pdu->msg[0] = i; + pdu->md.pdcp_sn = i; + pdu->N_bytes = rlc1.read_pdu(pdu->msg, 3); + TESTASSERT(pdu->N_bytes == 3); + + // If the TX window is full, we should RETX SN=0 + rlc_amd_pdu_header_t header = {}; + rlc_am_read_data_pdu_header(&pdu->msg, &pdu->N_bytes, &header); + TESTASSERT_EQ(header.sn, 0); + TESTASSERT_EQ(header.N_li, 0); + TESTASSERT_EQ(header.fi, 0); + } + } + // ACK more PDUs and advance VT(A). + // New PDUs should be available to read now. + { + rlc_status_pdu_t status = {}; + status.ack_sn = 5; + status.N_nack = 0; + + unique_byte_buffer_t status_buf = srsran::make_byte_buffer(); + TESTASSERT(status_buf != nullptr); + rlc_am_write_status_pdu(&status, status_buf.get()); + rlc1.write_pdu(status_buf->msg, status_buf->N_bytes); + + // Read new PDU + unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + TESTASSERT(pdu != nullptr); + pdu->N_bytes = rlc1.read_pdu(pdu->msg, 3); + TESTASSERT(pdu->N_bytes == 3); + + // If the TX window is no longer full, we should TX a new SN (SN=512) + rlc_amd_pdu_header_t header = {}; + rlc_am_read_data_pdu_header(&pdu->msg, &pdu->N_bytes, &header); + TESTASSERT_EQ(header.sn, 512); + TESTASSERT_EQ(header.N_li, 0); + TESTASSERT_EQ(header.fi, 0); + } + +#if HAVE_PCAP + pcap.close(); +#endif + + return SRSRAN_SUCCESS; +} + int main(int argc, char** argv) { // Setup the log message spy to intercept error and warning log entries from RLC @@ -3966,5 +4136,10 @@ int main(int argc, char** argv) printf("poll_retx_expiry_test failed\n"); exit(-1); }; + + if (full_window_check_test()) { + printf("full_window_check_test failed\n"); + exit(-1); + }; return SRSRAN_SUCCESS; }