diff --git a/lib/include/srsran/interfaces/rlc_interface_types.h b/lib/include/srsran/interfaces/rlc_interface_types.h index 2570d032d..0f4e069cc 100644 --- a/lib/include/srsran/interfaces/rlc_interface_types.h +++ b/lib/include/srsran/interfaces/rlc_interface_types.h @@ -225,6 +225,7 @@ public: rlc_cnfg.rat = srsran_rat_t::nr; rlc_cnfg.rlc_mode = rlc_mode_t::am; rlc_cnfg.am_nr.t_status_prohibit = 8; + rlc_cnfg.am_nr.max_retx_thresh = 4; rlc_cnfg.am_nr.t_reassembly = 35; rlc_cnfg.am_nr.poll_pdu = 4; return rlc_cnfg; diff --git a/lib/include/srsran/rlc/rlc_am_nr.h b/lib/include/srsran/rlc/rlc_am_nr.h index 0763a445d..09a31b44e 100644 --- a/lib/include/srsran/rlc/rlc_am_nr.h +++ b/lib/include/srsran/rlc/rlc_am_nr.h @@ -79,7 +79,6 @@ struct rlc_amd_tx_pdu_nr { uint32_t retx_count = RETX_COUNT_NOT_STARTED; struct pdu_segment { uint32_t so = 0; - uint32_t retx_count = 0; uint32_t payload_len = 0; }; std::list segment_list; diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index 3636ed5f6..c48af151a 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -723,11 +723,13 @@ void rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) // Process N_acks for (uint32_t nack_idx = 0; nack_idx < status.N_nack; nack_idx++) { + // TODO: Possibly loop NACK range if (st.tx_next_ack <= status.nacks[nack_idx].nack_sn && status.nacks[nack_idx].nack_sn <= st.tx_next) { auto nack = status.nacks[nack_idx]; uint32_t nack_sn = nack.nack_sn; if (tx_window.has_sn(nack_sn)) { - auto& pdu = tx_window[nack_sn]; + auto& pdu = tx_window[nack_sn]; + bool retx_enqueued = false; if (nack.has_so) { // NACK'ing missing bytes in SDU segment. @@ -739,39 +741,45 @@ void rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) segm != pdu.segment_list.end(); segm++) { if (segm->so >= nack.so_start && segm->so < nack.so_end) { + // TODO: Check if this segment is not already queued for retransmission rlc_amd_retx_t& retx = retx_queue.push(); retx.sn = nack_sn; retx.is_segment = true; retx.so_start = segm->so; retx.current_so = segm->so; retx.so_end = segm->so + segm->payload_len; + retx_enqueued = true; } } - - // TODO: Handle retx_count for segments - } else { // NACK'ing full SDU. // add to retx queue if it's not already there if (not retx_queue.has_sn(nack_sn)) { - // Increment retx_count and inform upper layers if needed - if (pdu.retx_count == RETX_COUNT_NOT_STARTED) { - // Set retx_count = 0 on first RE-transmission (38.322 Sec. 5.3.2) - pdu.retx_count = 0; - } else { - // Increment otherwise - pdu.retx_count++; - } - - check_sn_reached_max_retx(nack_sn); rlc_amd_retx_t& retx = retx_queue.push(); retx.sn = nack_sn; retx.is_segment = false; retx.so_start = 0; retx.current_so = 0; retx.so_end = pdu.sdu_buf->N_bytes; + retx_enqueued = true; } } + + if (retx_enqueued) { + // Increment retx_count + if (pdu.retx_count == RETX_COUNT_NOT_STARTED) { + // Set retx_count = 0 on first RE-transmission (38.322 Sec. 5.3.2) + pdu.retx_count = 0; + } else { + // Increment otherwise + pdu.retx_count++; + } + + // Inform upper layers if needed + check_sn_reached_max_retx(nack_sn); + + RlcInfo("Schedule SN=%d for retx", nack_sn); + } } } } diff --git a/lib/test/rlc/rlc_am_nr_test.cc b/lib/test/rlc/rlc_am_nr_test.cc index 79f92ab1f..b7fb60973 100644 --- a/lib/test/rlc/rlc_am_nr_test.cc +++ b/lib/test/rlc/rlc_am_nr_test.cc @@ -884,7 +884,8 @@ int retx_segment_test() } // This test checks whether RLC informs upper layer when max retransmission has been reached -int max_retx_test() +// due to lost SDUs as a whole +int max_retx_lost_sdu_test() { rlc_am_tester tester; timer_handler timers(8); @@ -951,6 +952,75 @@ int max_retx_test() return SRSRAN_SUCCESS; } +// This test checks whether RLC informs upper layer when max retransmission has been reached +// due to lost SDU segments +int max_retx_lost_segments_test() +{ + rlc_am_tester tester; + timer_handler timers(8); + int len = 0; + + auto& test_logger = srslog::fetch_basic_logger("TESTER "); + rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + srslog::fetch_basic_logger("RLC_AM_1").set_hex_dump_max_size(100); + srslog::fetch_basic_logger("RLC").set_hex_dump_max_size(100); + + const rlc_config_t rlc_cfg = rlc_config_t::default_rlc_am_nr_config(); + if (not rlc1.configure(rlc_cfg)) { + return SRSRAN_ERROR; + } + + // Push 2 SDUs into RLC1 + const uint32_t n_sdus = 2; + unique_byte_buffer_t sdu_bufs[n_sdus]; + for (uint32_t i = 0; i < n_sdus; i++) { + sdu_bufs[i] = srsran::make_byte_buffer(); + sdu_bufs[i]->msg[0] = i; // Write the index into the buffer + sdu_bufs[i]->N_bytes = 20; // Give each buffer a size of 20 bytes + sdu_bufs[i]->md.pdcp_sn = i; // PDCP SN for notifications + rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + // Read 4 PDUs from RLC1 (max 15 byte each) + const uint32_t n_pdus = 4; + byte_buffer_t pdu_bufs[n_pdus]; + for (uint32_t i = 0; i < n_pdus; i++) { + len = rlc1.read_pdu(pdu_bufs[i].msg, 15); // 2 byte header + 13 byte payload + pdu_bufs[i].N_bytes = len; + } + + TESTASSERT(0 == rlc1.get_buffer_state()); + + // Fake status PDU that ack SN=1 and nack SN=0 + rlc_am_nr_status_pdu_t fake_status = {}; + fake_status.ack_sn = 2; // delivered up to SN=1 + fake_status.N_nack = 1; // one SN was lost + fake_status.nacks[0].nack_sn = 0; // it was SN=0 that was lost + + // pack into PDU + byte_buffer_t status_pdu; + rlc_am_nr_write_status_pdu(fake_status, rlc_cfg.am_nr.tx_sn_field_length, &status_pdu); + + // Exceed the number of tolerated retransmissions by one additional retransmission + // to trigger notification of the higher protocol layers. Note that the initial transmission + // (before starting retransmissions) does not count. See TS 38.322 Sec. 5.3.2 + for (uint32_t retx_count = 0; retx_count < rlc_cfg.am_nr.max_retx_thresh + 1; ++retx_count) { + // we've not yet reached max attempts + TESTASSERT(tester.max_retx_triggered == false); + + // Write status PDU to RLC1 + rlc1.write_pdu(status_pdu.msg, status_pdu.N_bytes); + + byte_buffer_t pdu_buf; + len = rlc1.read_pdu(pdu_buf.msg, 3); // 2 byte header + 1 byte payload + } + + // Now maxRetx should have been triggered + TESTASSERT(tester.max_retx_triggered == true); + + return SRSRAN_SUCCESS; +} + // This test checks the correct functioning of RLC discard functionality int discard_test() { @@ -1084,7 +1154,8 @@ int main() TESTASSERT(basic_segmentation_test() == SRSRAN_SUCCESS); TESTASSERT(segment_retx_test() == SRSRAN_SUCCESS); TESTASSERT(retx_segment_test() == SRSRAN_SUCCESS); - TESTASSERT(max_retx_test() == SRSRAN_SUCCESS); + TESTASSERT(max_retx_lost_sdu_test() == SRSRAN_SUCCESS); + TESTASSERT(max_retx_lost_segments_test() == SRSRAN_SUCCESS); TESTASSERT(discard_test() == SRSRAN_SUCCESS); return SRSRAN_SUCCESS; }