From 444783e2f2a010fa45671b2e842a4b1c47133bdf Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Wed, 27 Apr 2022 19:17:01 +0100 Subject: [PATCH 01/25] lib,rlc_am_nr: added handling of nack ranges at RX --- lib/include/srsran/rlc/rlc_am_nr.h | 1 + lib/src/rlc/rlc_am_nr.cc | 209 +++++++++++++++-------------- 2 files changed, 112 insertions(+), 98 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_am_nr.h b/lib/include/srsran/rlc/rlc_am_nr.h index 619a3978e..61bdd88b9 100644 --- a/lib/include/srsran/rlc/rlc_am_nr.h +++ b/lib/include/srsran/rlc/rlc_am_nr.h @@ -95,6 +95,7 @@ public: bool configure(const rlc_config_t& cfg_) final; uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes) final; void handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) final; + void handle_nack(const rlc_status_nack_t& nack, std::set& retx_sn_set); void reestablish() final; void stop() final; diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index a05c8a8e3..b8eb57479 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -828,106 +828,27 @@ void rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) std::set retx_sn_set; // Set of PDU SNs added for retransmission (no duplicates) for (uint32_t nack_idx = 0; nack_idx < status.nacks.size(); nack_idx++) { if (status.nacks[nack_idx].has_nack_range) { - RlcWarning("Handling NACK ranges is not yet implemented. Ignoring NACK across %d SDU(s) starting from SN=%d", - status.nacks[nack_idx].nack_range, - status.nacks[nack_idx].nack_sn); - continue; - } - if (tx_mod_base_nr(st.tx_next_ack) <= tx_mod_base_nr(status.nacks[nack_idx].nack_sn) && - tx_mod_base_nr(status.nacks[nack_idx].nack_sn) <= tx_mod_base_nr(st.tx_next)) { - RlcDebug("Handling NACK for SN=%d", status.nacks[nack_idx].nack_sn); - 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]; - - if (nack.has_so) { - // NACK'ing missing bytes in SDU segment. - // Retransmit all SDU segments within those missing bytes. - if (pdu.segment_list.empty()) { - RlcError("Received NACK with SO, but there is no segment information. SN=%d", nack_sn); - } - bool segment_found = false; - for (const rlc_amd_tx_pdu_nr::pdu_segment& segm : pdu.segment_list) { - if (segm.so >= nack.so_start && segm.so <= nack.so_end) { - if (not retx_queue->has_sn(nack_sn, segm.so)) { - rlc_amd_retx_nr_t& retx = retx_queue->push(); - retx.sn = nack_sn; - retx.is_segment = true; - retx.so_start = segm.so; - retx.current_so = segm.so; - retx.segment_length = segm.payload_len; - retx_sn_set.insert(nack_sn); - RlcInfo("Scheduled RETX of SDU segment SN=%d, so_start=%d, segment_length=%d", - retx.sn, - retx.so_start, - retx.segment_length); - } else { - RlcInfo("Skip already scheduled RETX of SDU segment SN=%d, so_start=%d, segment_length=%d", - nack_sn, - segm.so, - segm.payload_len); - } - segment_found = true; - } - } - if (!segment_found) { - RlcWarning("Could not find segment for NACK_SN=%d. SO_start=%d, SO_end=%d", - status.nacks[nack_idx].nack_sn, - nack.so_start, - nack.so_end); - for (const rlc_amd_tx_pdu_nr::pdu_segment& segm : pdu.segment_list) { - RlcDebug( - "Segments for SN=%d. SO=%d, SO_end=%d", status.nacks[nack_idx].nack_sn, segm.so, segm.payload_len); - } - } - } else { - // NACK'ing full SDU. - // add to retx queue if it's not already there - if (not retx_queue->has_sn(nack_sn)) { - // Have we segmented the SDU already? - if ((*tx_window)[nack_sn].segment_list.empty()) { - rlc_amd_retx_nr_t& retx = retx_queue->push(); - retx.sn = nack_sn; - retx.is_segment = false; - retx.so_start = 0; - retx.current_so = 0; - retx.segment_length = pdu.sdu_buf->N_bytes; - retx_sn_set.insert(nack_sn); - RlcInfo("Scheduled RETX of SDU SN=%d", retx.sn); - } else { - RlcInfo("Scheduled RETX of SDU SN=%d", nack_sn); - retx_sn_set.insert(nack_sn); - for (auto segm : (*tx_window)[nack_sn].segment_list) { - rlc_amd_retx_nr_t& retx = retx_queue->push(); - retx.sn = nack_sn; - retx.is_segment = true; - retx.so_start = segm.so; - retx.current_so = segm.so; - retx.segment_length = segm.payload_len; - RlcInfo("Scheduled RETX of SDU Segment. SN=%d, SO=%d, len=%d", retx.sn, segm.so, segm.payload_len); - } - } - } else { - RlcInfo("RETX queue already has NACK_SN. SDU SN=%d, Tx_Next_Ack=%d, Tx_Next=%d", - status.nacks[nack_idx].nack_sn, - st.tx_next_ack, - st.tx_next); - } + RlcError("Handling NACK ranges is not yet implemented. Ignoring NACK across %d SDU(s) starting from SN=%d", + status.nacks[nack_idx].nack_range, + status.nacks[nack_idx].nack_sn); + for (uint32_t range_sn = status.nacks[nack_idx].nack_sn; + range_sn < status.nacks[nack_idx].nack_sn + status.nacks[nack_idx].nack_range; + range_sn++) { + rlc_status_nack_t nack = {}; + nack.nack_sn = range_sn; + if (range_sn == status.nacks[nack_idx].nack_sn) { + // First SN + nack.so_start = status.nacks[nack_idx].so_start; + } else if (range_sn == (status.nacks[nack_idx].nack_sn + status.nacks[nack_idx].nack_range - 1)) { + // Last SN + nack.so_end = status.nacks[nack_idx].so_end; } - } else { - RlcInfo("TX window does not contain NACK_SN. SDU SN=%d, Tx_Next_Ack=%d, Tx_Next=%d", - status.nacks[nack_idx].nack_sn, - st.tx_next_ack, - st.tx_next); - } // TX window containts NACK SN + handle_nack(nack, retx_sn_set); + } } else { - RlcInfo("RETX not in expected range. SDU SN=%d, Tx_Next_Ack=%d, Tx_Next=%d", - status.nacks[nack_idx].nack_sn, - st.tx_next_ack, - st.tx_next); - } // NACK SN within expected range - } // NACK loop + handle_nack(status.nacks[nack_idx], retx_sn_set); + } + } // Process retx_count and inform upper layers if needed for (uint32_t retx_sn : retx_sn_set) { @@ -952,6 +873,98 @@ void rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) notify_info_vec.clear(); } +void rlc_am_nr_tx::handle_nack(const rlc_status_nack_t& nack, std::set& retx_sn_set) +{ + if (tx_mod_base_nr(st.tx_next_ack) <= tx_mod_base_nr(nack.nack_sn) && + tx_mod_base_nr(nack.nack_sn) <= tx_mod_base_nr(st.tx_next)) { + RlcDebug("Handling NACK for SN=%d", nack.nack_sn); + if (tx_window->has_sn(nack.nack_sn)) { + auto& pdu = (*tx_window)[nack.nack_sn]; + + if (nack.has_so) { + // NACK'ing missing bytes in SDU segment. + // Retransmit all SDU segments within those missing bytes. + if (pdu.segment_list.empty()) { + RlcError("Received NACK with SO, but there is no segment information. SN=%d", nack.nack_sn); + } + bool segment_found = false; + for (const rlc_amd_tx_pdu_nr::pdu_segment& segm : pdu.segment_list) { + if (segm.so >= nack.so_start && segm.so <= nack.so_end) { + if (not retx_queue->has_sn(nack.nack_sn, segm.so)) { + rlc_amd_retx_nr_t& retx = retx_queue->push(); + retx.sn = nack.nack_sn; + retx.is_segment = true; + retx.so_start = segm.so; + retx.current_so = segm.so; + retx.segment_length = segm.payload_len; + retx_sn_set.insert(nack.nack_sn); + RlcInfo("Scheduled RETX of SDU segment SN=%d, so_start=%d, segment_length=%d", + retx.sn, + retx.so_start, + retx.segment_length); + } else { + RlcInfo("Skip already scheduled RETX of SDU segment SN=%d, so_start=%d, segment_length=%d", + nack.nack_sn, + segm.so, + segm.payload_len); + } + segment_found = true; + } + } + if (!segment_found) { + RlcWarning("Could not find segment for NACK_SN=%d. SO_start=%d, SO_end=%d", + nack.nack_sn, + nack.so_start, + nack.so_end); + for (const rlc_amd_tx_pdu_nr::pdu_segment& segm : pdu.segment_list) { + RlcDebug("Segments for SN=%d. SO=%d, SO_end=%d", nack.nack_sn, segm.so, segm.payload_len); + } + } + } else { + // NACK'ing full SDU. + // add to retx queue if it's not already there + if (not retx_queue->has_sn(nack.nack_sn)) { + // Have we segmented the SDU already? + if ((*tx_window)[nack.nack_sn].segment_list.empty()) { + rlc_amd_retx_nr_t& retx = retx_queue->push(); + retx.sn = nack.nack_sn; + retx.is_segment = false; + retx.so_start = 0; + retx.current_so = 0; + retx.segment_length = pdu.sdu_buf->N_bytes; + retx_sn_set.insert(nack.nack_sn); + RlcInfo("Scheduled RETX of SDU SN=%d", retx.sn); + } else { + RlcInfo("Scheduled RETX of SDU SN=%d", nack.nack_sn); + retx_sn_set.insert(nack.nack_sn); + for (auto segm : (*tx_window)[nack.nack_sn].segment_list) { + rlc_amd_retx_nr_t& retx = retx_queue->push(); + retx.sn = nack.nack_sn; + retx.is_segment = true; + retx.so_start = segm.so; + retx.current_so = segm.so; + retx.segment_length = segm.payload_len; + RlcInfo("Scheduled RETX of SDU Segment. SN=%d, SO=%d, len=%d", retx.sn, segm.so, segm.payload_len); + } + } + } else { + RlcInfo("RETX queue already has NACK_SN. SDU SN=%d, Tx_Next_Ack=%d, Tx_Next=%d", + nack.nack_sn, + st.tx_next_ack, + st.tx_next); + } + } + } else { + RlcInfo("TX window does not contain NACK_SN. SDU SN=%d, Tx_Next_Ack=%d, Tx_Next=%d", + nack.nack_sn, + st.tx_next_ack, + st.tx_next); + } // TX window containts NACK SN + } else { + RlcInfo( + "RETX not in expected range. SDU SN=%d, Tx_Next_Ack=%d, Tx_Next=%d", nack.nack_sn, st.tx_next_ack, st.tx_next); + } // NACK SN within expected range +} /** * Helper to check if a SN has reached the max reTx threshold * From 855ab8f7fdca0bf489dc4b223d9e1368e52dd5e3 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Wed, 27 Apr 2022 19:49:50 +0100 Subject: [PATCH 02/25] lib,rlc_am_nr: added unit test to nack ranges --- lib/test/rlc/rlc_am_nr_test.cc | 94 ++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/lib/test/rlc/rlc_am_nr_test.cc b/lib/test/rlc/rlc_am_nr_test.cc index 2529e0b5f..9eccf3350 100644 --- a/lib/test/rlc/rlc_am_nr_test.cc +++ b/lib/test/rlc/rlc_am_nr_test.cc @@ -2462,6 +2462,99 @@ bool poll_retx_expiry(rlc_am_nr_sn_size_t sn_size) return SRSRAN_SUCCESS; } +int rx_nack_range_test(rlc_am_nr_sn_size_t sn_size) +{ + rlc_am_tester tester; + timer_handler timers(8); + + 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); + rlc_am rlc2(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + std::string str = "Rx NACK range test (" + std::to_string(to_number(sn_size)) + " bit SN)"; + test_delimit_logger delimiter(str.c_str()); + + rlc_am_nr_tx* tx1 = dynamic_cast(rlc1.get_tx()); + rlc_am_nr_rx* rx1 = dynamic_cast(rlc1.get_rx()); + rlc_am_nr_tx* tx2 = dynamic_cast(rlc2.get_tx()); + rlc_am_nr_rx* rx2 = dynamic_cast(rlc2.get_rx()); + + auto rlc_cnfg = rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)); + rlc_cnfg.am_nr.t_poll_retx = -1; + if (not rlc1.configure(rlc_cnfg)) { + return -1; + } + + // after configuring entity + TESTASSERT(0 == rlc1.get_buffer_state()); + + int n_sdu_bufs = 5; + int n_pdu_bufs = 15; + + // Push 5 SDUs into RLC1 + std::vector sdu_bufs(n_sdu_bufs); + constexpr uint32_t payload_size = 3; // Give the SDU the size of 3 bytes + uint32_t header_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + for (int i = 0; i < n_sdu_bufs; 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 = payload_size; // Give each buffer a size of 3 bytes + sdu_bufs[i]->md.pdcp_sn = i; // PDCP SN for notifications + rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + uint32_t expected_buffer_state = (header_size + payload_size) * n_sdu_bufs; + TESTASSERT_EQ(expected_buffer_state, rlc1.get_buffer_state()); + + constexpr uint32_t so_size = 2; + constexpr uint32_t segment_size = 1; + uint32_t pdu_size_first = header_size + segment_size; + uint32_t pdu_size_continued = header_size + so_size + segment_size; + + // Read 15 PDUs from RLC1 + std::vector pdu_bufs(n_pdu_bufs); + for (int i = 0; i < n_pdu_bufs; i++) { + // First also test buffer state + uint32_t remaining_total_bytes = (payload_size * n_sdu_bufs) - (i * segment_size); + uint32_t remaining_full_sdus = remaining_total_bytes / payload_size; + uint32_t remaining_seg_bytes = remaining_total_bytes % payload_size; + + uint32_t buffer_state_full_sdus = (header_size + payload_size) * remaining_full_sdus; + uint32_t buffer_state_seg_sdu = remaining_seg_bytes == 0 ? 0 : (header_size + so_size + remaining_seg_bytes); + expected_buffer_state = buffer_state_full_sdus + buffer_state_seg_sdu; + TESTASSERT_EQ(expected_buffer_state, rlc1.get_buffer_state()); + + pdu_bufs[i] = srsran::make_byte_buffer(); + if (i == 0 || i == 3 || i == 6 || i == 9 || i == 12) { + // First segment, no SO + uint32_t len = rlc1.read_pdu(pdu_bufs[i]->msg, pdu_size_first); // 2 bytes for header + 1 byte payload + pdu_bufs[i]->N_bytes = len; + TESTASSERT_EQ(pdu_size_first, len); + } else { + // Middle or last segment, SO present + uint32_t len = rlc1.read_pdu(pdu_bufs[i]->msg, pdu_size_continued); // 4 bytes for header + 1 byte payload + pdu_bufs[i]->N_bytes = len; + TESTASSERT_EQ(pdu_size_continued, len); + } + } + + // Deliver dummy status report with nack range betwen PDU 6 and 10. + rlc_am_nr_status_pdu_t status(sn_size); + + rlc_status_nack_t nack = {}; + nack.nack_sn = 1; + nack.has_nack_range = true; + nack.nack_range = 3; + nack.so_start = 2; + nack.so_end = 1; + status.push_nack(nack); + byte_buffer_t status_pdu; + rlc_am_nr_write_status_pdu(status, sn_size, &status_pdu); + + rlc1.write_pdu(status_pdu.msg, status_pdu.N_bytes); + TESTASSERT_EQ(0, rlc1.get_buffer_state()); + return SRSRAN_SUCCESS; +} + int main() { // Setup the log message spy to intercept error and warning log entries from RLC @@ -2506,6 +2599,7 @@ int main() TESTASSERT(poll_byte(sn_size) == SRSRAN_SUCCESS); TESTASSERT(poll_retx(sn_size) == SRSRAN_SUCCESS); TESTASSERT(poll_retx_expiry(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(rx_nack_range_test(sn_size) == SRSRAN_SUCCESS); } return SRSRAN_SUCCESS; } From c386d895778695b8047355b2142d757fed593683 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Wed, 27 Apr 2022 21:25:06 +0100 Subject: [PATCH 03/25] lib,rlc_am_nr: fix nack ranges with so --- lib/src/rlc/rlc_am_nr.cc | 6 +++++- lib/test/rlc/rlc_am_nr_test.cc | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index b8eb57479..4d14c7a30 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -838,10 +838,14 @@ void rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) nack.nack_sn = range_sn; if (range_sn == status.nacks[nack_idx].nack_sn) { // First SN + nack.has_so = true; nack.so_start = status.nacks[nack_idx].so_start; + nack.so_end = so_end_of_sdu; } else if (range_sn == (status.nacks[nack_idx].nack_sn + status.nacks[nack_idx].nack_range - 1)) { // Last SN - nack.so_end = status.nacks[nack_idx].so_end; + nack.has_so = true; + nack.so_start = 0; + nack.so_end = status.nacks[nack_idx].so_end; } handle_nack(nack, retx_sn_set); } diff --git a/lib/test/rlc/rlc_am_nr_test.cc b/lib/test/rlc/rlc_am_nr_test.cc index 9eccf3350..dbb8edac7 100644 --- a/lib/test/rlc/rlc_am_nr_test.cc +++ b/lib/test/rlc/rlc_am_nr_test.cc @@ -2544,8 +2544,9 @@ int rx_nack_range_test(rlc_am_nr_sn_size_t sn_size) nack.nack_sn = 1; nack.has_nack_range = true; nack.nack_range = 3; + nack.has_so = true; nack.so_start = 2; - nack.so_end = 1; + nack.so_end = 0; status.push_nack(nack); byte_buffer_t status_pdu; rlc_am_nr_write_status_pdu(status, sn_size, &status_pdu); From de67d88ca9c25bff2725dca085eceb8b2e55141f Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Wed, 27 Apr 2022 21:27:52 +0100 Subject: [PATCH 04/25] lib,rlc_am_nr: fix buffer state checker in rx status report with ranges test --- lib/test/rlc/rlc_am_nr_test.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/test/rlc/rlc_am_nr_test.cc b/lib/test/rlc/rlc_am_nr_test.cc index dbb8edac7..661d93c87 100644 --- a/lib/test/rlc/rlc_am_nr_test.cc +++ b/lib/test/rlc/rlc_am_nr_test.cc @@ -2552,7 +2552,8 @@ int rx_nack_range_test(rlc_am_nr_sn_size_t sn_size) rlc_am_nr_write_status_pdu(status, sn_size, &status_pdu); rlc1.write_pdu(status_pdu.msg, status_pdu.N_bytes); - TESTASSERT_EQ(0, rlc1.get_buffer_state()); + + TESTASSERT_EQ(2 * pdu_size_first + 3 * pdu_size_continued, rlc1.get_buffer_state()); return SRSRAN_SUCCESS; } From 6d8357bee950e1037831a5c13c21e7e341eb0d74 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Wed, 27 Apr 2022 21:38:15 +0100 Subject: [PATCH 05/25] lib,rlc_am_nr: deleted error log for unhandled NACK ranges --- lib/src/rlc/rlc_am_nr.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index 4d14c7a30..d5d729516 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -828,9 +828,6 @@ void rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) std::set retx_sn_set; // Set of PDU SNs added for retransmission (no duplicates) for (uint32_t nack_idx = 0; nack_idx < status.nacks.size(); nack_idx++) { if (status.nacks[nack_idx].has_nack_range) { - RlcError("Handling NACK ranges is not yet implemented. Ignoring NACK across %d SDU(s) starting from SN=%d", - status.nacks[nack_idx].nack_range, - status.nacks[nack_idx].nack_sn); for (uint32_t range_sn = status.nacks[nack_idx].nack_sn; range_sn < status.nacks[nack_idx].nack_sn + status.nacks[nack_idx].nack_range; range_sn++) { From e01bbb8f7978fb6d096f0049ee2cd0bcfaea918b Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Thu, 28 Apr 2022 11:21:36 +0100 Subject: [PATCH 06/25] lib,rlc_am_nr: support also NACK ranges without SO_start/end. Fix for NACK ranges of sizee == 1 --- lib/src/rlc/rlc_am_nr.cc | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index d5d729516..2a979cf53 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -833,16 +833,18 @@ void rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) range_sn++) { rlc_status_nack_t nack = {}; nack.nack_sn = range_sn; - if (range_sn == status.nacks[nack_idx].nack_sn) { - // First SN - nack.has_so = true; - nack.so_start = status.nacks[nack_idx].so_start; - nack.so_end = so_end_of_sdu; - } else if (range_sn == (status.nacks[nack_idx].nack_sn + status.nacks[nack_idx].nack_range - 1)) { - // Last SN - nack.has_so = true; - nack.so_start = 0; - nack.so_end = status.nacks[nack_idx].so_end; + if (status.nacks[nack_idx].has_so) { + if (range_sn == status.nacks[nack_idx].nack_sn) { + // First SN + nack.has_so = true; + nack.so_start = status.nacks[nack_idx].so_start; + nack.so_end = so_end_of_sdu; + } else if (range_sn == (status.nacks[nack_idx].nack_sn + status.nacks[nack_idx].nack_range - 1)) { + // Last SN + nack.has_so = true; + // This might be first+last item at the same time, so don't change so_start here + nack.so_end = status.nacks[nack_idx].so_end; + } } handle_nack(nack, retx_sn_set); } From 283199d54f51399d8e4b8b5c77c3eb8e5e872b85 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Thu, 28 Apr 2022 11:25:37 +0100 Subject: [PATCH 07/25] lib,rlc_am_nr: added unit test for nack ranges without SO --- lib/test/rlc/rlc_am_nr_test.cc | 97 +++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/lib/test/rlc/rlc_am_nr_test.cc b/lib/test/rlc/rlc_am_nr_test.cc index 661d93c87..667266872 100644 --- a/lib/test/rlc/rlc_am_nr_test.cc +++ b/lib/test/rlc/rlc_am_nr_test.cc @@ -2462,7 +2462,99 @@ bool poll_retx_expiry(rlc_am_nr_sn_size_t sn_size) return SRSRAN_SUCCESS; } -int rx_nack_range_test(rlc_am_nr_sn_size_t sn_size) +int rx_nack_range_no_so_test(rlc_am_nr_sn_size_t sn_size) +{ + rlc_am_tester tester; + timer_handler timers(8); + + 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); + rlc_am rlc2(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + std::string str = "Rx NACK range test (" + std::to_string(to_number(sn_size)) + " bit SN)"; + test_delimit_logger delimiter(str.c_str()); + + rlc_am_nr_tx* tx1 = dynamic_cast(rlc1.get_tx()); + rlc_am_nr_rx* rx1 = dynamic_cast(rlc1.get_rx()); + rlc_am_nr_tx* tx2 = dynamic_cast(rlc2.get_tx()); + rlc_am_nr_rx* rx2 = dynamic_cast(rlc2.get_rx()); + + auto rlc_cnfg = rlc_config_t::default_rlc_am_nr_config(to_number(sn_size)); + rlc_cnfg.am_nr.t_poll_retx = -1; + if (not rlc1.configure(rlc_cnfg)) { + return -1; + } + + // after configuring entity + TESTASSERT(0 == rlc1.get_buffer_state()); + + int n_sdu_bufs = 5; + int n_pdu_bufs = 15; + + // Push 5 SDUs into RLC1 + std::vector sdu_bufs(n_sdu_bufs); + constexpr uint32_t payload_size = 3; // Give the SDU the size of 3 bytes + uint32_t header_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + for (int i = 0; i < n_sdu_bufs; 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 = payload_size; // Give each buffer a size of 3 bytes + sdu_bufs[i]->md.pdcp_sn = i; // PDCP SN for notifications + rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + uint32_t expected_buffer_state = (header_size + payload_size) * n_sdu_bufs; + TESTASSERT_EQ(expected_buffer_state, rlc1.get_buffer_state()); + + constexpr uint32_t so_size = 2; + constexpr uint32_t segment_size = 1; + uint32_t pdu_size_first = header_size + segment_size; + uint32_t pdu_size_continued = header_size + so_size + segment_size; + + // Read 15 PDUs from RLC1 + std::vector pdu_bufs(n_pdu_bufs); + for (int i = 0; i < n_pdu_bufs; i++) { + // First also test buffer state + uint32_t remaining_total_bytes = (payload_size * n_sdu_bufs) - (i * segment_size); + uint32_t remaining_full_sdus = remaining_total_bytes / payload_size; + uint32_t remaining_seg_bytes = remaining_total_bytes % payload_size; + + uint32_t buffer_state_full_sdus = (header_size + payload_size) * remaining_full_sdus; + uint32_t buffer_state_seg_sdu = remaining_seg_bytes == 0 ? 0 : (header_size + so_size + remaining_seg_bytes); + expected_buffer_state = buffer_state_full_sdus + buffer_state_seg_sdu; + TESTASSERT_EQ(expected_buffer_state, rlc1.get_buffer_state()); + + pdu_bufs[i] = srsran::make_byte_buffer(); + if (i == 0 || i == 3 || i == 6 || i == 9 || i == 12) { + // First segment, no SO + uint32_t len = rlc1.read_pdu(pdu_bufs[i]->msg, pdu_size_first); // 2 bytes for header + 1 byte payload + pdu_bufs[i]->N_bytes = len; + TESTASSERT_EQ(pdu_size_first, len); + } else { + // Middle or last segment, SO present + uint32_t len = rlc1.read_pdu(pdu_bufs[i]->msg, pdu_size_continued); // 4 bytes for header + 1 byte payload + pdu_bufs[i]->N_bytes = len; + TESTASSERT_EQ(pdu_size_continued, len); + } + } + + // Deliver dummy status report with nack range betwen PDU 6 and 10. + rlc_am_nr_status_pdu_t status(sn_size); + + rlc_status_nack_t nack = {}; + nack.nack_sn = 1; + nack.has_nack_range = true; + nack.nack_range = 3; + status.push_nack(nack); + byte_buffer_t status_pdu; + rlc_am_nr_write_status_pdu(status, sn_size, &status_pdu); + + rlc1.write_pdu(status_pdu.msg, status_pdu.N_bytes); + + TESTASSERT_EQ(3 * pdu_size_first + 6 * pdu_size_continued, rlc1.get_buffer_state()); + return SRSRAN_SUCCESS; +} + +int rx_nack_range_with_so_test(rlc_am_nr_sn_size_t sn_size) { rlc_am_tester tester; timer_handler timers(8); @@ -2601,7 +2693,8 @@ int main() TESTASSERT(poll_byte(sn_size) == SRSRAN_SUCCESS); TESTASSERT(poll_retx(sn_size) == SRSRAN_SUCCESS); TESTASSERT(poll_retx_expiry(sn_size) == SRSRAN_SUCCESS); - TESTASSERT(rx_nack_range_test(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(rx_nack_range_no_so_test(sn_size) == SRSRAN_SUCCESS); + TESTASSERT(rx_nack_range_with_so_test(sn_size) == SRSRAN_SUCCESS); } return SRSRAN_SUCCESS; } From e9156d4ba2a51be82e52f3caf796ed3e65c82df7 Mon Sep 17 00:00:00 2001 From: Robert Falkenberg Date: Fri, 29 Apr 2022 16:23:09 +0200 Subject: [PATCH 08/25] lib,rlc_am_nr: status PDU creation supports NACK range --- lib/include/srsran/rlc/rlc_common.h | 2 + lib/src/rlc/rlc_am_nr.cc | 5 +- lib/src/rlc/rlc_am_nr_packing.cc | 79 ++++++++++++++++++++++++++++- 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_common.h b/lib/include/srsran/rlc/rlc_common.h index 796704823..0703afe03 100644 --- a/lib/include/srsran/rlc/rlc_common.h +++ b/lib/include/srsran/rlc/rlc_common.h @@ -165,6 +165,8 @@ public: // NACK helper (for LTE and NR) struct rlc_status_nack_t { + const static uint16_t so_end_of_sdu = 0xFFFF; + uint32_t nack_sn; // Sequence Number (SN) of first missing SDU bool has_so; // NACKs continuous sequence of bytes [so_start..so_end] uint16_t so_start; // First missing byte in SDU with SN=nack_sn diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index 2a979cf53..44f682968 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -23,7 +23,6 @@ namespace srsran { const static uint32_t max_tx_queue_size = 256; -const static uint32_t so_end_of_sdu = 0xFFFF; /**************************************************************************** * RLC AM NR entity @@ -838,7 +837,7 @@ void rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) // First SN nack.has_so = true; nack.so_start = status.nacks[nack_idx].so_start; - nack.so_end = so_end_of_sdu; + nack.so_end = rlc_status_nack_t::so_end_of_sdu; } else if (range_sn == (status.nacks[nack_idx].nack_sn + status.nacks[nack_idx].nack_range - 1)) { // Last SN nack.has_so = true; @@ -1743,7 +1742,7 @@ uint32_t rlc_am_nr_rx::get_status_pdu(rlc_am_nr_status_pdu_t* status, uint32_t m nack.nack_sn = i; nack.has_so = true; nack.so_start = last_so; - nack.so_end = so_end_of_sdu; + nack.so_end = rlc_status_nack_t::so_end_of_sdu; status->push_nack(nack); RlcDebug( "Final segment missing. NACK_SN=%d. SO_start=%d, SO_end=%d", nack.nack_sn, nack.so_start, nack.so_end); diff --git a/lib/src/rlc/rlc_am_nr_packing.cc b/lib/src/rlc/rlc_am_nr_packing.cc index 72054c26c..e1469d21d 100644 --- a/lib/src/rlc/rlc_am_nr_packing.cc +++ b/lib/src/rlc/rlc_am_nr_packing.cc @@ -32,10 +32,85 @@ void rlc_am_nr_status_pdu_t::reset() packed_size_ = rlc_am_nr_status_pdu_sizeof_header_ack_sn; } +static bool is_continuous_sequence(const rlc_status_nack_t& left, const rlc_status_nack_t& right) +{ + // SN must be continuous + if (right.nack_sn != left.has_nack_range ? left.nack_sn + left.nack_range : left.nack_sn + 1) { + return false; + } + + // Segments on left side (if present) must reach the end of sdu + if (left.has_so && left.so_end != rlc_status_nack_t::so_end_of_sdu) { + return false; + } + + // Segments on right side (if present) must start from the beginning + if (right.has_so && right.so_start != 0) { + return false; + } + + return true; +} + void rlc_am_nr_status_pdu_t::push_nack(const rlc_status_nack_t& nack) { - nacks_.push_back(nack); - packed_size_ += nack_size(nack); + if (nacks_.size() == 0) { + nacks_.push_back(nack); + packed_size_ += nack_size(nack); + return; + } + + rlc_status_nack_t& prev = nacks_.back(); + if (is_continuous_sequence(prev, nack) == false) { + nacks_.push_back(nack); + packed_size_ += nack_size(nack); + return; + } + + // expand previous NACK + // subtract size of previous NACK (add updated size later) + packed_size_ -= nack_size(prev); + + // enable and update NACK range + if (nack.has_nack_range == true) { + if (prev.has_nack_range == true) { + // [NACK range][NACK range] + prev.nack_range += nack.nack_range; + } else { + // [NACK SDU][NACK range] + prev.nack_range = nack.nack_range + 1; + prev.has_nack_range = true; + } + } else { + if (prev.has_nack_range == true) { + // [NACK range][NACK SDU] + prev.nack_range++; + } else { + // [NACK SDU][NACK SDU] + prev.nack_range = 2; + prev.has_nack_range = true; + } + } + + // enable and update segment offsets (if required) + if (nack.has_so == true) { + if (prev.has_so == false) { + // [NACK SDU][NACK segm] + prev.has_so = true; + prev.so_start = 0; + } + // [NACK SDU][NACK segm] or [NACK segm][NACK segm] + prev.so_end = nack.so_end; + } else { + if (prev.has_so == true) { + // [NACK segm][NACK SDU] + prev.so_end = rlc_status_nack_t::so_end_of_sdu; + } + // [NACK segm][NACK SDU] or [NACK SDU][NACK SDU] + } + + // add updated size + packed_size_ += nack_size(prev); } bool rlc_am_nr_status_pdu_t::trim(uint32_t max_packed_size) From ecc995bd4b2fae52ba1569e12b623cc53f6442c8 Mon Sep 17 00:00:00 2001 From: Robert Falkenberg Date: Mon, 2 May 2022 14:55:33 +0200 Subject: [PATCH 09/25] lib,rlc_am_nr: merge NACKs across SN overflows --- lib/include/srsran/rlc/rlc_am_nr_packing.h | 7 +++++-- lib/src/rlc/rlc_am_nr_packing.cc | 7 ++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_am_nr_packing.h b/lib/include/srsran/rlc/rlc_am_nr_packing.h index f70bf6c14..b9a275ba6 100644 --- a/lib/include/srsran/rlc/rlc_am_nr_packing.h +++ b/lib/include/srsran/rlc/rlc_am_nr_packing.h @@ -90,6 +90,8 @@ class rlc_am_nr_status_pdu_t private: /// Stored SN size required to compute the packed size rlc_am_nr_sn_size_t sn_size = rlc_am_nr_sn_size_t::nulltype; + /// Stored modulus to determine continuous sequences across SN overflows + uint32_t mod_nr = cardinality(rlc_am_nr_sn_size_t::nulltype); /// Internal NACK container; keep in sync with packed_size_ std::vector nacks_ = {}; /// Stores the current packed size; sync on each change of nacks_ @@ -109,8 +111,9 @@ public: const uint32_t& packed_size = packed_size_; rlc_am_nr_status_pdu_t(rlc_am_nr_sn_size_t sn_size); - void reset(); - void push_nack(const rlc_status_nack_t& nack); + void reset(); + bool is_continuous_sequence(const rlc_status_nack_t& left, const rlc_status_nack_t& right) const; + void push_nack(const rlc_status_nack_t& nack); const std::vector& get_nacks() const { return nacks_; } uint32_t get_packed_size() const { return packed_size; } bool trim(uint32_t max_packed_size); diff --git a/lib/src/rlc/rlc_am_nr_packing.cc b/lib/src/rlc/rlc_am_nr_packing.cc index e1469d21d..b7ba0a867 100644 --- a/lib/src/rlc/rlc_am_nr_packing.cc +++ b/lib/src/rlc/rlc_am_nr_packing.cc @@ -19,7 +19,8 @@ namespace srsran { * Container implementation for pack/unpack functions ***************************************************************************/ -rlc_am_nr_status_pdu_t::rlc_am_nr_status_pdu_t(rlc_am_nr_sn_size_t sn_size) : sn_size(sn_size) +rlc_am_nr_status_pdu_t::rlc_am_nr_status_pdu_t(rlc_am_nr_sn_size_t sn_size) : + sn_size(sn_size), mod_nr(cardinality(sn_size)) { nacks_.reserve(RLC_AM_NR_TYP_NACKS); } @@ -32,10 +33,10 @@ void rlc_am_nr_status_pdu_t::reset() packed_size_ = rlc_am_nr_status_pdu_sizeof_header_ack_sn; } -static bool is_continuous_sequence(const rlc_status_nack_t& left, const rlc_status_nack_t& right) +bool rlc_am_nr_status_pdu_t::is_continuous_sequence(const rlc_status_nack_t& left, const rlc_status_nack_t& right) const { // SN must be continuous - if (right.nack_sn != left.has_nack_range ? left.nack_sn + left.nack_range : left.nack_sn + 1) { + if (right.nack_sn != ((left.has_nack_range ? left.nack_sn + left.nack_range : (left.nack_sn + 1)) % mod_nr)) { return false; } From 9a34d4d81b5f0e6df3f1fc765cf31e2a03b6b84e Mon Sep 17 00:00:00 2001 From: Robert Falkenberg Date: Mon, 2 May 2022 14:56:08 +0200 Subject: [PATCH 10/25] lib,rlc_am_nr: unit tests for NACK merging --- lib/include/srsran/rlc/rlc_common.h | 9 + lib/test/rlc/rlc_am_nr_pdu_test.cc | 2024 +++++++++++++++++++++++++++ 2 files changed, 2033 insertions(+) diff --git a/lib/include/srsran/rlc/rlc_common.h b/lib/include/srsran/rlc/rlc_common.h index 0703afe03..1c35fc467 100644 --- a/lib/include/srsran/rlc/rlc_common.h +++ b/lib/include/srsran/rlc/rlc_common.h @@ -183,6 +183,15 @@ struct rlc_status_nack_t { has_nack_range = false; nack_range = 0; } + + bool equals(const rlc_status_nack_t& other) const + { + return nack_sn == other.nack_sn && has_so == other.has_so && so_start == other.so_start && so_end == other.so_end && + has_nack_range == other.has_nack_range && nack_range == other.nack_range; + } + + bool operator==(const rlc_status_nack_t& other) const { return equals(other); } + bool operator!=(const rlc_status_nack_t& other) const { return not equals(other); } }; // STATUS PDU diff --git a/lib/test/rlc/rlc_am_nr_pdu_test.cc b/lib/test/rlc/rlc_am_nr_pdu_test.cc index 977337b4a..edfc166ad 100644 --- a/lib/test/rlc/rlc_am_nr_pdu_test.cc +++ b/lib/test/rlc/rlc_am_nr_pdu_test.cc @@ -433,6 +433,1990 @@ int rlc_am_nr_control_pdu_12bit_sn_test_nack_range() return SRSRAN_SUCCESS; } +// Test merge of NACKs upon status PDU creation -- previous NACK: non-range; next NACK: non-range +int rlc_am_nr_control_pdu_test_nack_merge_sdu_sdu(rlc_am_nr_sn_size_t sn_size) +{ + test_delimit_logger delimiter("Control PDU ({} bit SN) test NACK merge: SDU + SDU", to_number(sn_size)); + + const uint16_t so_end_of_sdu = rlc_status_nack_t::so_end_of_sdu; + const uint32_t mod_nr = cardinality(sn_size); + const uint32_t min_size = 3; + const uint32_t nack_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + const uint32_t so_size = 4; + const uint32_t range_size = 1; + + // Case: [...][NACK SDU] + [NACK SDU] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK SDU] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size, status_pdu.packed_size); + + // Add next NACK: [NACK SDU] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(false, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(2, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK SDU] + [NACK SDU] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK SDU] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size, status_pdu.packed_size); + + // Add next NACK: [NACK SDU] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1002; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size, status_pdu.packed_size); + TESTASSERT(prev_nack == status_pdu.nacks.front()); + TESTASSERT(next_nack == status_pdu.nacks.back()); + } + + // Case: [...][NACK SDU] + [NACK SDU] (continuous: merge with previous element) -- with SN overflow + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK SDU] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = mod_nr - 1; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size, status_pdu.packed_size); + + // Add next NACK: [NACK SDU] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 0; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(false, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(2, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK SDU] + [NACK SDU] (non-continuous, SN gap: append as is) -- with SN overflow + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK SDU] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = mod_nr - 1; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size, status_pdu.packed_size); + + // Add next NACK: [NACK SDU] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size, status_pdu.packed_size); + TESTASSERT(prev_nack == status_pdu.nacks.front()); + TESTASSERT(next_nack == status_pdu.nacks.back()); + } + + // Case: [...][NACK SDU] + [NACK segm] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK SDU] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size, status_pdu.packed_size); + + // Add next NACK: [NACK segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 50; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(next_nack.so_end, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(2, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK SDU] + [NACK segm] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK SDU] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size, status_pdu.packed_size); + + // Add next NACK: [NACK segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1002; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 50; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + so_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK SDU] + [NACK segm] (non-continuous, SO gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK SDU] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size, status_pdu.packed_size); + + // Add next NACK: [NACK segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = true; + next_nack.so_start = 1; + next_nack.so_end = 50; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + so_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK segm] + [NACK SDU] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size, status_pdu.packed_size); + + // Add next NACK: [NACK SDU] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(prev_nack.so_start, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(so_end_of_sdu, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(2, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK segm] + [NACK SDU] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + + // Add next NACK: [NACK SDU] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1002; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + so_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK segm] + [NACK SDU] (non-continuous, SO gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = 99; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size, status_pdu.packed_size); + + // Add next NACK: [NACK SDU] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + so_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK segm] + [NACK segm] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size, status_pdu.packed_size); + + // Add next NACK: [NACK segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 22; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(prev_nack.so_start, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(next_nack.so_end, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(2, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK segm] + [NACK segm] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size, status_pdu.packed_size); + + // Add next NACK: [NACK segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1002; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 22; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * so_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK segm] + [NACK segm] (non-continuous, SO gap (left): append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = 99; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size, status_pdu.packed_size); + + // Add next NACK: [NACK segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 22; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * so_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK segm] + [NACK segm] (non-continuous, SO gap (right): append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size, status_pdu.packed_size); + + // Add next NACK: [NACK segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = true; + next_nack.so_start = 5; + next_nack.so_end = 22; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * so_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + return SRSRAN_SUCCESS; +} + +// Test merge of NACKs upon status PDU creation -- previous NACK: range; next NACK: non-range +int rlc_am_nr_control_pdu_test_nack_merge_range_sdu(rlc_am_nr_sn_size_t sn_size) +{ + test_delimit_logger delimiter("Control PDU ({} bit SN) test NACK merge: range + SDU", to_number(sn_size)); + + const uint16_t so_end_of_sdu = rlc_status_nack_t::so_end_of_sdu; + const uint32_t mod_nr = cardinality(sn_size); + const uint32_t min_size = 3; + const uint32_t nack_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + const uint32_t so_size = 4; + const uint32_t range_size = 1; + + // Case: [...][NACK range] + [NACK SDU] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK SDU] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(false, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(prev_nack.nack_range + 1, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK range] + [NACK SDU] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK SDU] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1006; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + range_size, status_pdu.packed_size); + TESTASSERT(prev_nack == status_pdu.nacks.front()); + TESTASSERT(next_nack == status_pdu.nacks.back()); + } + + // Case: [...][NACK range] + [NACK SDU] (continuous: merge with previous element) -- with SN overflow + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = mod_nr - 1; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK SDU] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 4; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(false, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(prev_nack.nack_range + 1, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK range] + [NACK SDU] (non-continuous, SN gap: append as is) -- with SN overflow + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = mod_nr - 1; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK SDU] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 5; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + range_size, status_pdu.packed_size); + TESTASSERT(prev_nack == status_pdu.nacks.front()); + TESTASSERT(next_nack == status_pdu.nacks.back()); + } + + // Case: [...][NACK range] + [NACK segm] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 50; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(next_nack.so_end, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(prev_nack.nack_range + 1, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK range] + [NACK segm] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1006; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 50; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + range_size + so_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK range] + [NACK segm] (non-continuous, SO gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = true; + next_nack.so_start = 1; + next_nack.so_end = 50; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + range_size + so_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK range+segm] + [NACK SDU] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range+segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK SDU] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(prev_nack.so_start, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(so_end_of_sdu, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(prev_nack.nack_range + 1, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK range+segm] + [NACK SDU] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range+segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + + // Add next NACK: [NACK SDU] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1006; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(min_size + 2 * nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK range+segm] + [NACK SDU] (non-continuous, SO gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range+segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = 99; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK SDU] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK range+segm] + [NACK segm] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range+segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 22; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(prev_nack.so_start, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(next_nack.so_end, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(prev_nack.nack_range + 1, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK range+segm] + [NACK segm] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range+segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1006; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 22; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * so_size + range_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK range+segm] + [NACK segm] (non-continuous, SO gap (left): append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range+segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = 99; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 22; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * so_size + range_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK range+segm] + [NACK segm] (non-continuous, SO gap (right): append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range+segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = true; + next_nack.so_start = 5; + next_nack.so_end = 22; + next_nack.has_nack_range = false; + next_nack.nack_range = 0; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * so_size + range_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + return SRSRAN_SUCCESS; +} + +// Test merge of NACKs upon status PDU creation -- previous NACK: non-range; next NACK: range +int rlc_am_nr_control_pdu_test_nack_merge_sdu_range(rlc_am_nr_sn_size_t sn_size) +{ + test_delimit_logger delimiter("Control PDU ({} bit SN) test NACK merge: SDU + range", to_number(sn_size)); + + const uint16_t so_end_of_sdu = rlc_status_nack_t::so_end_of_sdu; + const uint32_t mod_nr = cardinality(sn_size); + const uint32_t min_size = 3; + const uint32_t nack_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + const uint32_t so_size = 4; + const uint32_t range_size = 1; + + // Case: [...][NACK SDU] + [NACK range] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK SDU] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size, status_pdu.packed_size); + + // Add next NACK: [NACK range] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(false, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(3, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK SDU] + [NACK range] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK SDU] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size, status_pdu.packed_size); + + // Add next NACK: [NACK range] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1002; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + range_size, status_pdu.packed_size); + TESTASSERT(prev_nack == status_pdu.nacks.front()); + TESTASSERT(next_nack == status_pdu.nacks.back()); + } + + // Case: [...][NACK SDU] + [NACK range] (continuous: merge with previous element) -- with SN overflow + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK SDU] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = mod_nr - 1; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size, status_pdu.packed_size); + + // Add next NACK: [NACK range] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 0; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(false, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(3, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK SDU] + [NACK range] (non-continuous, SN gap: append as is) -- with SN overflow + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK SDU] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = mod_nr - 1; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size, status_pdu.packed_size); + + // Add next NACK: [NACK range] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + range_size, status_pdu.packed_size); + TESTASSERT(prev_nack == status_pdu.nacks.front()); + TESTASSERT(next_nack == status_pdu.nacks.back()); + } + + // Case: [...][NACK SDU] + [NACK range+segm] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK SDU] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size, status_pdu.packed_size); + + // Add next NACK: [NACK range+segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 50; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(next_nack.so_end, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(3, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK SDU] + [NACK range+segm] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK SDU] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size, status_pdu.packed_size); + + // Add next NACK: [NACK range+segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1002; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 50; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK SDU] + [NACK range+segm] (non-continuous, SO gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK SDU] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size, status_pdu.packed_size); + + // Add next NACK: [NACK range+segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = true; + next_nack.so_start = 1; + next_nack.so_end = 50; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK segm] + [NACK range] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size, status_pdu.packed_size); + + // Add next NACK: [NACK range] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(prev_nack.so_start, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(so_end_of_sdu, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(3, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK segm] + [NACK range] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + + // Add next NACK: [NACK range] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1002; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK segm] + [NACK range] (non-continuous, SO gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = 99; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size, status_pdu.packed_size); + + // Add next NACK: [NACK range] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK segm] + [NACK range+segm] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size, status_pdu.packed_size); + + // Add next NACK: [NACK range+segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 22; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(prev_nack.so_start, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(next_nack.so_end, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(3, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK segm] + [NACK range+segm] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size, status_pdu.packed_size); + + // Add next NACK: [NACK range+segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1002; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 22; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * so_size + range_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK segm] + [NACK range+segm] (non-continuous, SO gap (left): append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = 99; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size, status_pdu.packed_size); + + // Add next NACK: [NACK range+segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 22; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * so_size + range_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK segm] + [NACK range+segm] (non-continuous, SO gap (right): append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = false; + prev_nack.nack_range = 0; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size, status_pdu.packed_size); + + // Add next NACK: [NACK range+segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1001; + next_nack.has_so = true; + next_nack.so_start = 5; + next_nack.so_end = 22; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * so_size + range_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + return SRSRAN_SUCCESS; +} + +// Test merge of NACKs upon status PDU creation -- previous NACK: range; next NACK: range +int rlc_am_nr_control_pdu_test_nack_merge_range_range(rlc_am_nr_sn_size_t sn_size) +{ + test_delimit_logger delimiter("Control PDU ({} bit SN) test NACK merge: range + SDU", to_number(sn_size)); + + const uint16_t so_end_of_sdu = rlc_status_nack_t::so_end_of_sdu; + const uint32_t mod_nr = cardinality(sn_size); + const uint32_t min_size = 3; + const uint32_t nack_size = sn_size == rlc_am_nr_sn_size_t::size12bits ? 2 : 3; + const uint32_t so_size = 4; + const uint32_t range_size = 1; + + // Case: [...][NACK range] + [NACK range] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK range] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(false, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(prev_nack.nack_range + next_nack.nack_range, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK range] + [NACK range] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK range] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1006; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * range_size, status_pdu.packed_size); + TESTASSERT(prev_nack == status_pdu.nacks.front()); + TESTASSERT(next_nack == status_pdu.nacks.back()); + } + + // Case: [...][NACK range] + [NACK range] (continuous: merge with previous element) -- with SN overflow + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = mod_nr - 1; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK range] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 4; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(false, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(prev_nack.nack_range + next_nack.nack_range, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK range] + [NACK range] (non-continuous, SN gap: append as is) -- with SN overflow + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = mod_nr - 1; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK range] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 5; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * range_size, status_pdu.packed_size); + TESTASSERT(prev_nack == status_pdu.nacks.front()); + TESTASSERT(next_nack == status_pdu.nacks.back()); + } + + // Case: [...][NACK range] + [NACK range+segm] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK range+segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 50; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(0, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(next_nack.so_end, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(prev_nack.nack_range + next_nack.nack_range, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK range] + [NACK range+segm] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK range+segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1006; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 50; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * range_size + so_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK range] + [NACK range+segm] (non-continuous, SO gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = false; + prev_nack.so_start = 0; + prev_nack.so_end = 0; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK range+segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = true; + next_nack.so_start = 1; + next_nack.so_end = 50; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * range_size + so_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK range+segm] + [NACK range] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range+segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK range] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(prev_nack.so_start, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(so_end_of_sdu, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(prev_nack.nack_range + next_nack.nack_range, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK range+segm] + [NACK range] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range+segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + + // Add next NACK: [NACK range] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1006; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(min_size + 2 * nack_size + so_size + 2 * range_size, status_pdu.packed_size); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK range+segm] + [NACK range] (non-continuous, SO gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range+segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = 99; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK range] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = false; + next_nack.so_start = 0; + next_nack.so_end = 0; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + so_size + 2 * range_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK range+segm] + [NACK range+segm] (continuous: merge with previous element) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range+segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK range+segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 22; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + TESTASSERT_EQ(prev_nack.nack_sn, status_pdu.nacks.back().nack_sn); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_so); + TESTASSERT_EQ(prev_nack.so_start, status_pdu.nacks.back().so_start); + TESTASSERT_EQ(next_nack.so_end, status_pdu.nacks.back().so_end); + TESTASSERT_EQ(true, status_pdu.nacks.back().has_nack_range); + TESTASSERT_EQ(prev_nack.nack_range + next_nack.nack_range, status_pdu.nacks.back().nack_range); + } + + // Case: [...][NACK range+segm] + [NACK range+segm] (non-continuous, SN gap: append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range+segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK range+segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1006; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 22; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * so_size + 2 * range_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK range+segm] + [NACK range+segm] (non-continuous, SO gap (left): append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range+segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = 99; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK range+segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = true; + next_nack.so_start = 0; + next_nack.so_end = 22; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * so_size + 2 * range_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + // Case: [...][NACK range+segm] + [NACK range+segm] (non-continuous, SO gap (right): append as is) + { + rlc_am_nr_status_pdu_t status_pdu(sn_size); + status_pdu.ack_sn = 2000; + TESTASSERT_EQ(0, status_pdu.nacks.size()); + + // Prepare status_pdu.nacks: [...][NACK range+segm] + rlc_status_nack_t prev_nack; + prev_nack.nack_sn = 1000; + prev_nack.has_so = true; + prev_nack.so_start = 7; + prev_nack.so_end = so_end_of_sdu; + prev_nack.has_nack_range = true; + prev_nack.nack_range = 5; + status_pdu.push_nack(prev_nack); + TESTASSERT_EQ(1, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + nack_size + so_size + range_size, status_pdu.packed_size); + + // Add next NACK: [NACK range+segm] + rlc_status_nack_t next_nack; + next_nack.nack_sn = 1005; + next_nack.has_so = true; + next_nack.so_start = 5; + next_nack.so_end = 22; + next_nack.has_nack_range = true; + next_nack.nack_range = 2; + status_pdu.push_nack(next_nack); + TESTASSERT_EQ(2, status_pdu.nacks.size()); + TESTASSERT_EQ(min_size + 2 * nack_size + 2 * so_size + 2 * range_size, status_pdu.packed_size); + TESTASSERT(status_pdu.nacks.front() == prev_nack); + TESTASSERT(status_pdu.nacks.back() == next_nack); + } + + return SRSRAN_SUCCESS; +} + // Test status PDU for correct trimming and estimation of packed size // 1) Test init, copy and reset // 2) Test step-wise growth and trimming of status PDU while covering several corner cases @@ -1012,6 +2996,26 @@ int main(int argc, char** argv) return SRSRAN_ERROR; } + if (rlc_am_nr_control_pdu_test_nack_merge_sdu_sdu(rlc_am_nr_sn_size_t::size12bits)) { + fprintf(stderr, "rlc_am_nr_control_pdu_test_nack_merge_sdu_sdu(size12bits) failed.\n"); + return SRSRAN_ERROR; + } + + if (rlc_am_nr_control_pdu_test_nack_merge_range_sdu(rlc_am_nr_sn_size_t::size12bits)) { + fprintf(stderr, "rlc_am_nr_control_pdu_test_nack_merge_range_sdu(size12bits) failed.\n"); + return SRSRAN_ERROR; + } + + if (rlc_am_nr_control_pdu_test_nack_merge_sdu_range(rlc_am_nr_sn_size_t::size12bits)) { + fprintf(stderr, "rlc_am_nr_control_pdu_test_nack_merge_sdu_range(size12bits) failed.\n"); + return SRSRAN_ERROR; + } + + if (rlc_am_nr_control_pdu_test_nack_merge_range_range(rlc_am_nr_sn_size_t::size12bits)) { + fprintf(stderr, "rlc_am_nr_control_pdu_test_nack_merge_range_range(size12bits) failed.\n"); + return SRSRAN_ERROR; + } + if (rlc_am_nr_control_pdu_test_trimming(rlc_am_nr_sn_size_t::size12bits)) { fprintf(stderr, "rlc_am_nr_control_pdu_test_trimming(size12bits) failed.\n"); return SRSRAN_ERROR; @@ -1047,6 +3051,26 @@ int main(int argc, char** argv) return SRSRAN_ERROR; } + if (rlc_am_nr_control_pdu_test_nack_merge_sdu_sdu(rlc_am_nr_sn_size_t::size18bits)) { + fprintf(stderr, "rlc_am_nr_control_pdu_test_nack_merge_sdu_sdu(size18bits) failed.\n"); + return SRSRAN_ERROR; + } + + if (rlc_am_nr_control_pdu_test_nack_merge_range_sdu(rlc_am_nr_sn_size_t::size18bits)) { + fprintf(stderr, "rlc_am_nr_control_pdu_test_nack_merge_range_sdu(size18bits) failed.\n"); + return SRSRAN_ERROR; + } + + if (rlc_am_nr_control_pdu_test_nack_merge_sdu_range(rlc_am_nr_sn_size_t::size18bits)) { + fprintf(stderr, "rlc_am_nr_control_pdu_test_nack_merge_sdu_range(size18bits) failed.\n"); + return SRSRAN_ERROR; + } + + if (rlc_am_nr_control_pdu_test_nack_merge_range_range(rlc_am_nr_sn_size_t::size18bits)) { + fprintf(stderr, "rlc_am_nr_control_pdu_test_nack_merge_range_range(size18bits) failed.\n"); + return SRSRAN_ERROR; + } + if (rlc_am_nr_control_pdu_test_trimming(rlc_am_nr_sn_size_t::size18bits)) { fprintf(stderr, "rlc_am_nr_control_pdu_test_trimming(size18bits) failed.\n"); return SRSRAN_ERROR; From ebbecbe27cf929d65cfa28dd83df79e23915b591 Mon Sep 17 00:00:00 2001 From: Robert Falkenberg Date: Mon, 9 May 2022 16:50:53 +0200 Subject: [PATCH 11/25] lib,rlc_am_nr: cosmetic change -- clang format --- lib/include/srsran/rlc/rlc_am_nr_packing.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_am_nr_packing.h b/lib/include/srsran/rlc/rlc_am_nr_packing.h index b9a275ba6..47e5bc12b 100644 --- a/lib/include/srsran/rlc/rlc_am_nr_packing.h +++ b/lib/include/srsran/rlc/rlc_am_nr_packing.h @@ -19,8 +19,8 @@ namespace srsran { -const uint32_t INVALID_RLC_SN = 0xFFFFFFFF; -const uint32_t RETX_COUNT_NOT_STARTED = 0xFFFFFFFF; +const uint32_t INVALID_RLC_SN = 0xFFFFFFFF; +const uint32_t RETX_COUNT_NOT_STARTED = 0xFFFFFFFF; ///< AM NR PDU header struct rlc_am_nr_pdu_header_t { @@ -97,7 +97,7 @@ private: /// Stores the current packed size; sync on each change of nacks_ uint32_t packed_size_ = rlc_am_nr_status_pdu_sizeof_header_ack_sn; - void refresh_packed_size(); + void refresh_packed_size(); uint32_t nack_size(const rlc_status_nack_t& nack) const; public: From 1fecae9b5ab630695654d553055696e55220722c Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Thu, 9 Dec 2021 14:34:12 +0000 Subject: [PATCH 12/25] lib,pdcp_nr: added ifdef to select pdcp_entity_nr --- lib/src/pdcp/pdcp.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/pdcp/pdcp.cc b/lib/src/pdcp/pdcp.cc index 36c6f674b..b423aa286 100644 --- a/lib/src/pdcp/pdcp.cc +++ b/lib/src/pdcp/pdcp.cc @@ -111,7 +111,12 @@ int pdcp::add_bearer(uint32_t lcid, const pdcp_config_t& cfg) if (cfg.rat == srsran::srsran_rat_t::lte) { entity.reset(new pdcp_entity_lte{rlc, rrc, gw, task_sched, logger, lcid}); } else if (cfg.rat == srsran::srsran_rat_t::nr) { +#ifdef USE_PDCP_NR +#pragma message "Compiling with PDCP NR entity" + entity.reset(new pdcp_entity_nr{rlc, rrc, gw, task_sched, logger, lcid}); +#else entity.reset(new pdcp_entity_lte{rlc, rrc, gw, task_sched, logger, lcid}); +#endif } if (not entity->configure(cfg)) { From 3620308940271c5a3beb0b37257bdd61dc8f9c67 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Fri, 10 Dec 2021 11:53:02 +0000 Subject: [PATCH 13/25] lib,pdcp_nr: added options to select re-ordering timer --- .../srsran/interfaces/pdcp_interface_types.h | 75 ++++++------ lib/src/asn1/rrc_nr_utils.cc | 113 +++++++++++++++++- lib/src/pdcp/pdcp_entity_nr.cc | 6 + srsgnb/src/stack/rrc/rrc_nr_ue.cc | 12 +- 4 files changed, 159 insertions(+), 47 deletions(-) diff --git a/lib/include/srsran/interfaces/pdcp_interface_types.h b/lib/include/srsran/interfaces/pdcp_interface_types.h index 6507e14ba..01e483bf5 100644 --- a/lib/include/srsran/interfaces/pdcp_interface_types.h +++ b/lib/include/srsran/interfaces/pdcp_interface_types.h @@ -58,42 +58,43 @@ static const char* pdcp_pdu_type_text[PDCP_PDU_TYPE_N_ITEMS] = {"PDCP Report PDU // Taken from PDCP-Config (TS 38.331 version 15.2.1) enum class pdcp_t_reordering_t { - ms0 = 0, - ms1 = 1, - ms2 = 2, - ms4 = 4, - ms5 = 5, - ms8 = 8, - ms10 = 10, - ms15 = 15, - ms20 = 20, - ms30 = 30, - ms40 = 40, - ms50 = 50, - ms60 = 60, - ms80 = 80, - ms100 = 100, - ms120 = 120, - ms140 = 140, - ms160 = 160, - ms180 = 180, - ms200 = 200, - ms220 = 220, - ms240 = 240, - ms260 = 260, - ms280 = 280, - ms300 = 300, - ms500 = 500, - ms750 = 750, - ms1000 = 1000, - ms1250 = 1250, - ms1500 = 1500, - ms1750 = 1750, - ms2000 = 2000, - ms2250 = 2250, - ms2500 = 2500, - ms2750 = 2750, - ms3000 = 3000 + ms0 = 0, + ms1 = 1, + ms2 = 2, + ms4 = 4, + ms5 = 5, + ms8 = 8, + ms10 = 10, + ms15 = 15, + ms20 = 20, + ms30 = 30, + ms40 = 40, + ms50 = 50, + ms60 = 60, + ms80 = 80, + ms100 = 100, + ms120 = 120, + ms140 = 140, + ms160 = 160, + ms180 = 180, + ms200 = 200, + ms220 = 220, + ms240 = 240, + ms260 = 260, + ms280 = 280, + ms300 = 300, + ms500 = 500, + ms750 = 750, + ms1000 = 1000, + ms1250 = 1250, + ms1500 = 1500, + ms1750 = 1750, + ms2000 = 2000, + ms2250 = 2250, + ms2500 = 2500, + ms2750 = 2750, + ms3000 = 3000, + infinity = -1 }; // Taken from PDCP-Config (TS 38.331 version 15.2.1) @@ -113,7 +114,7 @@ enum class pdcp_discard_timer_t { ms500 = 500, ms750 = 750, ms1500 = 1500, - infinity = 0 + infinity = -1 }; class pdcp_config_t diff --git a/lib/src/asn1/rrc_nr_utils.cc b/lib/src/asn1/rrc_nr_utils.cc index 050004088..62c23adaa 100644 --- a/lib/src/asn1/rrc_nr_utils.cc +++ b/lib/src/asn1/rrc_nr_utils.cc @@ -236,14 +236,119 @@ srsran::pdcp_config_t make_drb_pdcp_config_t(const uint8_t bearer_id, bool is_ue } } - pdcp_t_reordering_t t_reordering = pdcp_t_reordering_t::ms500; + pdcp_t_reordering_t t_reordering = pdcp_t_reordering_t::infinity; if (pdcp_cfg.t_reordering_present) { switch (pdcp_cfg.t_reordering.to_number()) { case 0: t_reordering = pdcp_t_reordering_t::ms0; break; - default: + case 1: + t_reordering = pdcp_t_reordering_t::ms1; + break; + case 2: + t_reordering = pdcp_t_reordering_t::ms2; + break; + case 4: + t_reordering = pdcp_t_reordering_t::ms4; + break; + case 5: + t_reordering = pdcp_t_reordering_t::ms5; + break; + case 8: + t_reordering = pdcp_t_reordering_t::ms8; + break; + case 10: + t_reordering = pdcp_t_reordering_t::ms10; + break; + case 15: + t_reordering = pdcp_t_reordering_t::ms15; + break; + case 20: + t_reordering = pdcp_t_reordering_t::ms20; + break; + case 30: + t_reordering = pdcp_t_reordering_t::ms30; + break; + case 40: + t_reordering = pdcp_t_reordering_t::ms40; + break; + case 50: + t_reordering = pdcp_t_reordering_t::ms50; + break; + case 60: + t_reordering = pdcp_t_reordering_t::ms60; + break; + case 80: + t_reordering = pdcp_t_reordering_t::ms80; + break; + case 100: + t_reordering = pdcp_t_reordering_t::ms100; + break; + case 120: + t_reordering = pdcp_t_reordering_t::ms120; + break; + case 140: + t_reordering = pdcp_t_reordering_t::ms140; + break; + case 160: + t_reordering = pdcp_t_reordering_t::ms160; + break; + case 180: + t_reordering = pdcp_t_reordering_t::ms180; + break; + case 200: + t_reordering = pdcp_t_reordering_t::ms200; + break; + case 220: + t_reordering = pdcp_t_reordering_t::ms220; + break; + case 240: + t_reordering = pdcp_t_reordering_t::ms240; + break; + case 260: + t_reordering = pdcp_t_reordering_t::ms260; + break; + case 280: + t_reordering = pdcp_t_reordering_t::ms280; + break; + case 300: + t_reordering = pdcp_t_reordering_t::ms300; + break; + case 500: t_reordering = pdcp_t_reordering_t::ms500; + break; + case 750: + t_reordering = pdcp_t_reordering_t::ms750; + break; + case 1000: + t_reordering = pdcp_t_reordering_t::ms1000; + break; + case 1250: + t_reordering = pdcp_t_reordering_t::ms1250; + break; + case 1500: + t_reordering = pdcp_t_reordering_t::ms1500; + break; + case 1750: + t_reordering = pdcp_t_reordering_t::ms1750; + break; + case 2000: + t_reordering = pdcp_t_reordering_t::ms2000; + break; + case 2250: + t_reordering = pdcp_t_reordering_t::ms2250; + break; + case 2500: + t_reordering = pdcp_t_reordering_t::ms2500; + break; + case 2750: + t_reordering = pdcp_t_reordering_t::ms2750; + break; + case 3000: + t_reordering = pdcp_t_reordering_t::ms3000; + break; + default: + t_reordering = pdcp_t_reordering_t::ms50; } } @@ -1148,7 +1253,7 @@ bool make_phy_zp_csi_rs_resource(const asn1::rrc_nr::zp_csi_rs_res_s& zp_csi_rs_ srsran_csi_rs_zp_resource_t* out_zp_csi_rs_resource) { srsran_csi_rs_zp_resource_t zp_csi_rs_resource = {}; - zp_csi_rs_resource.id = zp_csi_rs_res.zp_csi_rs_res_id; + zp_csi_rs_resource.id = zp_csi_rs_res.zp_csi_rs_res_id; switch (zp_csi_rs_res.res_map.freq_domain_alloc.type()) { case csi_rs_res_map_s::freq_domain_alloc_c_::types_opts::options::row1: zp_csi_rs_resource.resource_mapping.row = srsran_csi_rs_resource_mapping_row_1; @@ -1313,7 +1418,7 @@ bool make_phy_nzp_csi_rs_resource(const asn1::rrc_nr::nzp_csi_rs_res_s& asn1_nzp srsran_csi_rs_nzp_resource_t* out_csi_rs_nzp_resource) { srsran_csi_rs_nzp_resource_t csi_rs_nzp_resource = {}; - csi_rs_nzp_resource.id = asn1_nzp_csi_rs_res.nzp_csi_rs_res_id; + csi_rs_nzp_resource.id = asn1_nzp_csi_rs_res.nzp_csi_rs_res_id; switch (asn1_nzp_csi_rs_res.res_map.freq_domain_alloc.type()) { case csi_rs_res_map_s::freq_domain_alloc_c_::types_opts::options::row1: csi_rs_nzp_resource.resource_mapping.row = srsran_csi_rs_resource_mapping_row_1; diff --git a/lib/src/pdcp/pdcp_entity_nr.cc b/lib/src/pdcp/pdcp_entity_nr.cc index f447816c9..e38cf6de5 100644 --- a/lib/src/pdcp/pdcp_entity_nr.cc +++ b/lib/src/pdcp/pdcp_entity_nr.cc @@ -64,6 +64,12 @@ bool pdcp_entity_nr::configure(const pdcp_config_t& cnfg_) reordering_timer.set(static_cast(cfg.t_reordering), *reordering_fnc); } active = true; + logger.info("%s PDCP-NR entity configured. SN_LEN=%d, Discard timer %d, Re-ordering timer %d, RAT=%s", + rb_name, + cfg.sn_len, + cfg.discard_timer, + cfg.t_reordering, + to_string(cfg.rat)); return true; } diff --git a/srsgnb/src/stack/rrc/rrc_nr_ue.cc b/srsgnb/src/stack/rrc/rrc_nr_ue.cc index cebf25ca3..09e93d1de 100644 --- a/srsgnb/src/stack/rrc/rrc_nr_ue.cc +++ b/srsgnb/src/stack/rrc/rrc_nr_ue.cc @@ -775,12 +775,12 @@ int rrc_nr::ue::add_drb(uint32_t five_qi) radio_bearer_cfg_pack.drb_to_add_mod_list.resize(1); // configure fixed DRB1 - auto& drb_item = radio_bearer_cfg_pack.drb_to_add_mod_list[0]; - drb_item.drb_id = 1; - drb_item.cn_assoc_present = true; - drb_item.cn_assoc.set_eps_bearer_id() = 5; - drb_item.pdcp_cfg_present = true; - drb_item.pdcp_cfg = parent->cfg.five_qi_cfg[five_qi].pdcp_cfg; + auto& drb_item = radio_bearer_cfg_pack.drb_to_add_mod_list[0]; + drb_item.drb_id = 1; + drb_item.cn_assoc_present = true; + drb_item.cn_assoc.set_eps_bearer_id() = 5; + drb_item.pdcp_cfg_present = true; + drb_item.pdcp_cfg = parent->cfg.five_qi_cfg[five_qi].pdcp_cfg; // Add DRB1 to PDCP srsran::pdcp_config_t pdcp_cnfg = srsran::make_drb_pdcp_config_t(drb_item.drb_id, false, drb_item.pdcp_cfg); From c5d5d45574ecc3ab86347dbab8fc9d27d1815248 Mon Sep 17 00:00:00 2001 From: Robert Falkenberg Date: Tue, 10 May 2022 09:14:25 +0200 Subject: [PATCH 14/25] cmake: fix build for cmake option BUILD_SHARED_LIBS=ON The cmake option BUILD_SHARED_LIBS (off by default) causes all libraries to be built as shared libraries if not unless explicitly stated otherwise. Since test-helper libraries do not export their symbols, linking fails if built as shared library. Therefore, this change explicitly configures these helpers as STATIC. --- srsenb/test/rrc/CMakeLists.txt | 2 +- srsgnb/src/stack/mac/test/CMakeLists.txt | 4 ++-- srsgnb/src/stack/rrc/test/CMakeLists.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/srsenb/test/rrc/CMakeLists.txt b/srsenb/test/rrc/CMakeLists.txt index 06ecb4221..fb6694fd4 100644 --- a/srsenb/test/rrc/CMakeLists.txt +++ b/srsenb/test/rrc/CMakeLists.txt @@ -6,7 +6,7 @@ # the distribution. # -add_library(test_helpers test_helpers.cc) +add_library(test_helpers STATIC test_helpers.cc) target_link_libraries(test_helpers srsenb_rrc srsenb_common rrc_asn1 rrc_nr_asn1 s1ap_asn1 srsran_common enb_cfg_parser ${LIBCONFIGPP_LIBRARIES}) add_executable(rrc_meascfg_test rrc_meascfg_test.cc) diff --git a/srsgnb/src/stack/mac/test/CMakeLists.txt b/srsgnb/src/stack/mac/test/CMakeLists.txt index 6afaccc59..2441f5f8a 100644 --- a/srsgnb/src/stack/mac/test/CMakeLists.txt +++ b/srsgnb/src/stack/mac/test/CMakeLists.txt @@ -8,7 +8,7 @@ set_directory_properties(PROPERTIES LABELS "sched;nr") -add_library(sched_nr_test_suite sched_nr_common_test.cc sched_nr_ue_ded_test_suite.cc sched_nr_sim_ue.cc) +add_library(sched_nr_test_suite STATIC sched_nr_common_test.cc sched_nr_ue_ded_test_suite.cc sched_nr_sim_ue.cc) target_link_libraries(sched_nr_test_suite srsgnb_mac srsran_common rrc_nr_asn1) add_executable(sched_nr_parallel_test sched_nr_parallel_test.cc) @@ -53,4 +53,4 @@ target_link_libraries(sched_nr_test rrc_nr_asn1 srsran_common ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) -add_nr_test(sched_nr_test sched_nr_test) \ No newline at end of file +add_nr_test(sched_nr_test sched_nr_test) diff --git a/srsgnb/src/stack/rrc/test/CMakeLists.txt b/srsgnb/src/stack/rrc/test/CMakeLists.txt index 103b1bb38..efca40807 100644 --- a/srsgnb/src/stack/rrc/test/CMakeLists.txt +++ b/srsgnb/src/stack/rrc/test/CMakeLists.txt @@ -6,7 +6,7 @@ # the distribution. # -add_library(rrc_nr_test_helpers rrc_nr_test_helpers.cc) +add_library(rrc_nr_test_helpers STATIC rrc_nr_test_helpers.cc) add_executable(rrc_nr_test rrc_nr_test.cc) target_link_libraries(rrc_nr_test srsgnb_rrc srsgnb_rrc_config_utils srsran_common rrc_nr_asn1 rrc_nr_test_helpers srsgnb_mac ${ATOMIC_LIBS}) From d2b27a6f7d6057f0378640b19974169135e355c3 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Thu, 28 Apr 2022 18:06:40 +0100 Subject: [PATCH 15/25] srsgnb: added example SA srb config --- srsenb/rb.conf.example | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/srsenb/rb.conf.example b/srsenb/rb.conf.example index 8cab02bb6..3203c2fe2 100644 --- a/srsenb/rb.conf.example +++ b/srsenb/rb.conf.example @@ -95,6 +95,40 @@ qci_config = ( ); // 5G Section +// srb1_5g_config = { +// rlc_config = { +// ul_am = { +// sn_field_len = 12; +// t_poll_retx = 50; +// poll_pdu = 4; +// poll_byte = 3000; +// max_retx_thres = 4; +// }; +// dl_am = { +// sn_field_len = 12; +// t_reassembly = 50; +// t_status_prohibit = 50; +// }; +// }; +// } + +// srb2_5g_config = { +// rlc_config = { +// ul_am = { +// sn_field_len = 12; +// t_poll_retx = 50; +// poll_pdu = 4; +// poll_byte = 3000; +// max_retx_thres = 4; +// }; +// dl_am = { +// sn_field_len = 12; +// t_reassembly = 50; +// t_status_prohibit = 50; +// }; +// }; +// } + five_qi_config = ( { five_qi = 7; From 36354ef6ffd32b5d3ef540946aa6d9d4bd199d92 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Thu, 28 Apr 2022 18:07:20 +0100 Subject: [PATCH 16/25] srsgnb,cfg_parser: adding parsing for SA SRB configs --- srsenb/hdr/stack/rrc/rrc_config.h | 12 ++--- srsenb/rb.conf.example | 13 +++--- srsenb/src/enb_cfg_parser.cc | 65 ++++++++++++++++++++++++++++ srsenb/src/enb_cfg_parser.h | 12 +++++ srsgnb/hdr/stack/rrc/rrc_nr_config.h | 6 +++ 5 files changed, 94 insertions(+), 14 deletions(-) diff --git a/srsenb/hdr/stack/rrc/rrc_config.h b/srsenb/hdr/stack/rrc/rrc_config.h index aa402b37b..b6feefe1a 100644 --- a/srsenb/hdr/stack/rrc/rrc_config.h +++ b/srsenb/hdr/stack/rrc/rrc_config.h @@ -76,12 +76,12 @@ struct rrc_cfg_t { bool meas_cfg_present = false; srsran_cell_t cell; cell_list_t cell_list; - uint32_t num_nr_cells = 0; /// number of configured NR cells (used to configure RF) - uint32_t max_mac_dl_kos; - uint32_t max_mac_ul_kos; - uint32_t rlf_release_timer_ms; - srb_cfg_t srb1_cfg; - srb_cfg_t srb2_cfg; + uint32_t num_nr_cells = 0; /// number of configured NR cells (used to configure RF) + uint32_t max_mac_dl_kos; + uint32_t max_mac_ul_kos; + uint32_t rlf_release_timer_ms; + srb_cfg_t srb1_cfg; + srb_cfg_t srb2_cfg; rrc_endc_cfg_t endc_cfg; }; diff --git a/srsenb/rb.conf.example b/srsenb/rb.conf.example index 3203c2fe2..dc19c63f3 100644 --- a/srsenb/rb.conf.example +++ b/srsenb/rb.conf.example @@ -5,17 +5,16 @@ // srb1_config = { // rlc_config = { // ul_am = { +// sn_field_len = 12; // t_poll_retx = 45; // poll_pdu = -1; // poll_byte = -1; // max_retx_thresh = 4; // }; // dl_am = { -// t_reordering = 35; -// t_status_prohibit = 0; -// }; -// enb_specific = { -// dl_max_retx_thresh = 32; +// sn_field_len = 12; +// t_reassembly = 50; +// t_status_prohibit = 50; // }; // }; // } @@ -23,6 +22,7 @@ // srb2_config = { // rlc_config = { // ul_am = { +// sn_field_len = 12; // t_poll_retx = 45; // poll_pdu = -1; // poll_byte = -1; @@ -32,9 +32,6 @@ // t_reordering = 35; // t_status_prohibit = 0; // }; -// enb_specific = { -// dl_max_retx_thresh = 32; -// }; // }; // } diff --git a/srsenb/src/enb_cfg_parser.cc b/srsenb/src/enb_cfg_parser.cc index 8fd77c89d..d531b1019 100644 --- a/srsenb/src/enb_cfg_parser.cc +++ b/srsenb/src/enb_cfg_parser.cc @@ -635,6 +635,71 @@ int field_qci::parse(libconfig::Setting& root) return 0; } +int field_5g_srb::parse(libconfig::Setting& root) +{ + // Parse RLC AM section + asn1::rrc_nr::rlc_cfg_c* rlc_cfg = &cfg.rlc_cfg; + if (root.exists("ul_am") && root.exists("dl_am")) { + rlc_cfg->set_am(); + cfg.present = true; + } + + // RLC-UM Should not exist section + if (root.exists("ul_um") || root.exists("dl_um")) { + ERROR("Error SRBs must be AM."); + return SRSRAN_ERROR; + } + + // Parse RLC-AM section + if (root.exists("ul_am")) { + asn1::rrc_nr::ul_am_rlc_s& ul_am_rlc = rlc_cfg->am().ul_am_rlc; + + field_asn1_enum_number t_poll_retx("t_poll_retx", &ul_am_rlc.t_poll_retx); + if (t_poll_retx.parse(root["ul_am"])) { + ERROR("Error can't find t_poll_retx in section ul_am"); + return SRSRAN_ERROR; + } + + field_asn1_enum_number poll_pdu("poll_pdu", &ul_am_rlc.poll_pdu); + if (poll_pdu.parse(root["ul_am"])) { + ERROR("Error can't find poll_pdu in section ul_am"); + return SRSRAN_ERROR; + } + + field_asn1_enum_number poll_byte("poll_byte", &ul_am_rlc.poll_byte); + if (poll_byte.parse(root["ul_am"])) { + ERROR("Error can't find poll_byte in section ul_am"); + return SRSRAN_ERROR; + } + + field_asn1_enum_number max_retx_thresh("max_retx_thresh", + &ul_am_rlc.max_retx_thres); + if (max_retx_thresh.parse(root["ul_am"])) { + ERROR("Error can't find max_retx_thresh in section ul_am"); + return SRSRAN_ERROR; + } + } + + if (root.exists("dl_am")) { + asn1::rrc_nr::dl_am_rlc_s& dl_am_rlc = rlc_cfg->am().dl_am_rlc; + + field_asn1_enum_number t_reassembly("t_reassembly", &dl_am_rlc.t_reassembly); + if (t_reassembly.parse(root["dl_am"])) { + ERROR("Error can't find t_reordering in section dl_am"); + return SRSRAN_ERROR; + } + + field_asn1_enum_number t_status_prohibit("t_status_prohibit", + &dl_am_rlc.t_status_prohibit); + if (t_status_prohibit.parse(root["dl_am"])) { + ERROR("Error can't find t_status_prohibit in section dl_am"); + return SRSRAN_ERROR; + } + } + + return 0; +} + int field_five_qi::parse(libconfig::Setting& root) { uint32_t nof_five_qi = (uint32_t)root.getLength(); diff --git a/srsenb/src/enb_cfg_parser.h b/srsenb/src/enb_cfg_parser.h index 10b19ed6f..d1f0ae0ae 100644 --- a/srsenb/src/enb_cfg_parser.h +++ b/srsenb/src/enb_cfg_parser.h @@ -193,6 +193,18 @@ private: std::map& cfg; }; +class field_5g_srb final : public parser::field_itf +{ +public: + explicit field_5g_srb(srb_5g_cfg_t& cfg_) : cfg(cfg_) {} + const char* get_name() override { return "field_5g_srb"; } + + int parse(Setting& root) override; + +private: + srb_5g_cfg_t& cfg; +}; + class field_five_qi final : public parser::field_itf { public: diff --git a/srsgnb/hdr/stack/rrc/rrc_nr_config.h b/srsgnb/hdr/stack/rrc/rrc_nr_config.h index b4c7848f5..aedf3ad1c 100644 --- a/srsgnb/hdr/stack/rrc/rrc_nr_config.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr_config.h @@ -45,6 +45,12 @@ struct rrc_cell_cfg_nr_t { typedef std::vector rrc_cell_list_nr_t; +struct srb_5g_cfg_t { + int enb_dl_max_retx_thres = -1; + bool present = false; + asn1::rrc_nr::rlc_cfg_c rlc_cfg; +}; + struct rrc_nr_cfg_five_qi_t { bool configured = false; asn1::rrc_nr::pdcp_cfg_s pdcp_cfg; From 3ae6aae230104821556817156daa17c45d62e891 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Fri, 29 Apr 2022 15:32:20 +0100 Subject: [PATCH 17/25] gnb,rrc: starting to read srb configuration from config --- srsgnb/hdr/stack/rrc/rrc_nr_config.h | 3 ++ srsgnb/src/stack/rrc/cell_asn1_config.cc | 42 ++++++++++++++++-------- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/srsgnb/hdr/stack/rrc/rrc_nr_config.h b/srsgnb/hdr/stack/rrc/rrc_nr_config.h index aedf3ad1c..0b63e98db 100644 --- a/srsgnb/hdr/stack/rrc/rrc_nr_config.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr_config.h @@ -65,6 +65,9 @@ struct rrc_nr_cfg_t { uint16_t mnc; bool is_standalone; + srb_5g_cfg_t srb1_cfg; + srb_5g_cfg_t srb2_cfg; + std::map five_qi_cfg; std::array nea_preference_list; diff --git a/srsgnb/src/stack/rrc/cell_asn1_config.cc b/srsgnb/src/stack/rrc/cell_asn1_config.cc index 2cd893ad7..8ffce3f27 100644 --- a/srsgnb/src/stack/rrc/cell_asn1_config.cc +++ b/srsgnb/src/stack/rrc/cell_asn1_config.cc @@ -947,19 +947,35 @@ void fill_srb(const rrc_nr_cfg_t& cfg, srsran::nr_srb srb_id, asn1::rrc_nr::rlc_ out.served_radio_bearer_present = true; out.served_radio_bearer.set_srb_id() = (uint8_t)srb_id; - out.rlc_cfg_present = true; - auto& ul_am = out.rlc_cfg.set_am().ul_am_rlc; - ul_am.sn_field_len_present = true; - ul_am.sn_field_len.value = asn1::rrc_nr::sn_field_len_am_opts::size12; - ul_am.t_poll_retx.value = asn1::rrc_nr::t_poll_retx_opts::ms45; - ul_am.poll_pdu.value = asn1::rrc_nr::poll_pdu_opts::infinity; - ul_am.poll_byte.value = asn1::rrc_nr::poll_byte_opts::infinity; - ul_am.max_retx_thres.value = asn1::rrc_nr::ul_am_rlc_s::max_retx_thres_opts::t8; - auto& dl_am = out.rlc_cfg.am().dl_am_rlc; - dl_am.sn_field_len_present = true; - dl_am.sn_field_len.value = asn1::rrc_nr::sn_field_len_am_opts::size12; - dl_am.t_reassembly.value = t_reassembly_opts::ms35; - dl_am.t_status_prohibit.value = asn1::rrc_nr::t_status_prohibit_opts::ms0; + if (srb_id == srsran::nr_srb::srb1) { + if (cfg.srb1_cfg.present) { + out.rlc_cfg_present = true; + out.rlc_cfg = cfg.srb1_cfg.rlc_cfg; + } else { + out.rlc_cfg_present = false; + } + } else if (srb_id == srsran::nr_srb::srb2) { + if (cfg.srb2_cfg.present) { + out.rlc_cfg_present = true; + out.rlc_cfg = cfg.srb2_cfg.rlc_cfg; + } else { + out.rlc_cfg_present = false; + } + } else { + out.rlc_cfg_present = true; + auto& ul_am = out.rlc_cfg.set_am().ul_am_rlc; + ul_am.sn_field_len_present = true; + ul_am.sn_field_len.value = asn1::rrc_nr::sn_field_len_am_opts::size12; + ul_am.t_poll_retx.value = asn1::rrc_nr::t_poll_retx_opts::ms45; + ul_am.poll_pdu.value = asn1::rrc_nr::poll_pdu_opts::infinity; + ul_am.poll_byte.value = asn1::rrc_nr::poll_byte_opts::infinity; + ul_am.max_retx_thres.value = asn1::rrc_nr::ul_am_rlc_s::max_retx_thres_opts::t8; + auto& dl_am = out.rlc_cfg.am().dl_am_rlc; + dl_am.sn_field_len_present = true; + dl_am.sn_field_len.value = asn1::rrc_nr::sn_field_len_am_opts::size12; + dl_am.t_reassembly.value = t_reassembly_opts::ms35; + dl_am.t_status_prohibit.value = asn1::rrc_nr::t_status_prohibit_opts::ms0; + } // mac-LogicalChannelConfig -- Cond LCH-Setup out.mac_lc_ch_cfg_present = true; From ece3c69d45d8da3140a940fd4cd30c74b538cfe6 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Fri, 29 Apr 2022 16:56:33 +0100 Subject: [PATCH 18/25] gnb,rrc: make it possible to use default configs when SRB configs are not present. --- srsgnb/src/stack/rrc/rrc_nr_ue.cc | 25 ++++++++++++++++++------- srsue/src/stack/rrc_nr/rrc_nr.cc | 3 +++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/srsgnb/src/stack/rrc/rrc_nr_ue.cc b/srsgnb/src/stack/rrc/rrc_nr_ue.cc index 09e93d1de..cb6991daf 100644 --- a/srsgnb/src/stack/rrc/rrc_nr_ue.cc +++ b/srsgnb/src/stack/rrc/rrc_nr_ue.cc @@ -1409,13 +1409,24 @@ int rrc_nr::ue::update_rlc_bearers(const asn1::rrc_nr::cell_group_cfg_s& cell_gr // Add/Mod RLC radio bearers for (const rlc_bearer_cfg_s& rb : cell_group_diff.rlc_bearer_to_add_mod_list) { srsran::rlc_config_t rlc_cfg; - uint8_t rb_id = rb.served_radio_bearer.type().value == rlc_bearer_cfg_s::served_radio_bearer_c_::types_opts::drb_id - ? rb.served_radio_bearer.drb_id() - : rb.served_radio_bearer.srb_id(); - if (srsran::make_rlc_config_t(rb.rlc_cfg, rb_id, &rlc_cfg) != SRSRAN_SUCCESS) { - logger.error("Failed to build RLC config"); - // TODO: HANDLE - return SRSRAN_ERROR; + uint8_t rb_id = 0; + if (rb.served_radio_bearer.type().value == rlc_bearer_cfg_s::served_radio_bearer_c_::types_opts::srb_id) { + rb_id = rb.served_radio_bearer.srb_id(); + if (not rb.rlc_cfg_present) { + rlc_cfg = srsran::rlc_config_t::default_rlc_am_nr_config(); + } + } else { + rb_id = rb.served_radio_bearer.drb_id(); + if (not rb.rlc_cfg_present) { + logger.error("No RLC config for DRB"); + // TODO: HANDLE + return SRSRAN_ERROR; + } + if (srsran::make_rlc_config_t(rb.rlc_cfg, rb_id, &rlc_cfg) != SRSRAN_SUCCESS) { + logger.error("Failed to build RLC config"); + // TODO: HANDLE + return SRSRAN_ERROR; + } } parent->rlc->add_bearer(rnti, rb.lc_ch_id, rlc_cfg); } diff --git a/srsue/src/stack/rrc_nr/rrc_nr.cc b/srsue/src/stack/rrc_nr/rrc_nr.cc index 9a4186891..9a57422e5 100644 --- a/srsue/src/stack/rrc_nr/rrc_nr.cc +++ b/srsue/src/stack/rrc_nr/rrc_nr.cc @@ -1061,6 +1061,9 @@ bool rrc_nr::apply_rlc_add_mod(const rlc_bearer_cfg_s& rlc_bearer_cfg) logger.error("Failed to build RLC config"); return false; } + } else if (not is_drb) { + logger.error("Using default RLC configs for SRB%d", srb_id); + rlc_cfg = rlc_config_t::default_rlc_am_nr_config(); } else { logger.error("In RLC bearer cfg does not contain rlc cfg"); return false; From c737f75abbc5cef3d89d66a24229dcdefc5fb2af Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Fri, 29 Apr 2022 18:19:45 +0100 Subject: [PATCH 19/25] gnb,config: enable SRB config parser for SA --- srsenb/src/enb_cfg_parser.cc | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/srsenb/src/enb_cfg_parser.cc b/srsenb/src/enb_cfg_parser.cc index d531b1019..38b5ef9fd 100644 --- a/srsenb/src/enb_cfg_parser.cc +++ b/srsenb/src/enb_cfg_parser.cc @@ -672,7 +672,7 @@ int field_5g_srb::parse(libconfig::Setting& root) return SRSRAN_ERROR; } - field_asn1_enum_number max_retx_thresh("max_retx_thresh", + field_asn1_enum_number max_retx_thresh("max_retx_thres", &ul_am_rlc.max_retx_thres); if (max_retx_thresh.parse(root["ul_am"])) { ERROR("Error can't find max_retx_thresh in section ul_am"); @@ -2416,6 +2416,22 @@ int parse_rb(all_args_t* args_, rrc_cfg_t* rrc_cfg_, rrc_nr_cfg_t* rrc_nr_cfg_) parser::section qci("qci_config"); qci.add_field(new field_qci(rrc_cfg_->qci_cfg)); + parser::section srb1_5g("srb1_5g_config"); + bool srb1_5g_present = false; + srb1_5g.set_optional(&srb1_5g_present); + + parser::section srb1_5g_rlc_cfg("rlc_config"); + srb1_5g.add_subsection(&srb1_5g_rlc_cfg); + srb1_5g_rlc_cfg.add_field(new field_5g_srb(rrc_nr_cfg_->srb1_cfg)); + + parser::section srb2_5g("srb2_5g_config"); + bool srb2_5g_present = false; + srb2_5g.set_optional(&srb2_5g_present); + + parser::section srb2_5g_rlc_cfg("rlc_config"); + srb2_5g.add_subsection(&srb2_5g_rlc_cfg); + srb2_5g_rlc_cfg.add_field(new field_5g_srb(rrc_nr_cfg_->srb2_cfg)); + parser::section five_qi("five_qi_config"); five_qi.add_field(new field_five_qi(rrc_nr_cfg_->five_qi_cfg)); @@ -2424,6 +2440,8 @@ int parse_rb(all_args_t* args_, rrc_cfg_t* rrc_cfg_, rrc_nr_cfg_t* rrc_nr_cfg_) p.add_section(&srb1); p.add_section(&srb2); p.add_section(&qci); + p.add_section(&srb1_5g); + p.add_section(&srb2_5g); p.add_section(&five_qi); int ret = p.parse(); @@ -2433,6 +2451,8 @@ int parse_rb(all_args_t* args_, rrc_cfg_t* rrc_cfg_, rrc_nr_cfg_t* rrc_nr_cfg_) if (not srb2_present) { rrc_cfg_->srb2_cfg.rlc_cfg.set_default_value(); } + rrc_nr_cfg_->srb1_cfg.present = srb1_5g_present; + rrc_nr_cfg_->srb2_cfg.present = srb1_5g_present; return ret; } From f79b3e9435c8daa1a9f37452d6b525ca091f52ff Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Fri, 29 Apr 2022 19:23:05 +0100 Subject: [PATCH 20/25] gnb,rrc: fix SRB config generation when configuration is present in rb.conf --- srsgnb/src/stack/rrc/rrc_nr_ue.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/srsgnb/src/stack/rrc/rrc_nr_ue.cc b/srsgnb/src/stack/rrc/rrc_nr_ue.cc index cb6991daf..9ca216c8b 100644 --- a/srsgnb/src/stack/rrc/rrc_nr_ue.cc +++ b/srsgnb/src/stack/rrc/rrc_nr_ue.cc @@ -1414,6 +1414,12 @@ int rrc_nr::ue::update_rlc_bearers(const asn1::rrc_nr::cell_group_cfg_s& cell_gr rb_id = rb.served_radio_bearer.srb_id(); if (not rb.rlc_cfg_present) { rlc_cfg = srsran::rlc_config_t::default_rlc_am_nr_config(); + } else { + if (srsran::make_rlc_config_t(rb.rlc_cfg, rb_id, &rlc_cfg) != SRSRAN_SUCCESS) { + logger.error("Failed to build RLC config"); + // TODO: HANDLE + return SRSRAN_ERROR; + } } } else { rb_id = rb.served_radio_bearer.drb_id(); From b446fa87b04aeb10ab547ed00cf30a7eb4d9becd Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Fri, 29 Apr 2022 19:39:01 +0100 Subject: [PATCH 21/25] gnb,config: enable changing the SN length of SRBs --- srsenb/src/enb_cfg_parser.cc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/srsenb/src/enb_cfg_parser.cc b/srsenb/src/enb_cfg_parser.cc index 38b5ef9fd..9f4dd845e 100644 --- a/srsenb/src/enb_cfg_parser.cc +++ b/srsenb/src/enb_cfg_parser.cc @@ -654,6 +654,14 @@ int field_5g_srb::parse(libconfig::Setting& root) if (root.exists("ul_am")) { asn1::rrc_nr::ul_am_rlc_s& ul_am_rlc = rlc_cfg->am().ul_am_rlc; + // SN length + field_asn1_enum_number rlc_sn_size_ul("sn_field_len", &ul_am_rlc.sn_field_len); + if (rlc_sn_size_ul.parse(root["ul_am"]) == SRSRAN_ERROR) { + ul_am_rlc.sn_field_len_present = false; + } else { + ul_am_rlc.sn_field_len_present = true; + } + field_asn1_enum_number t_poll_retx("t_poll_retx", &ul_am_rlc.t_poll_retx); if (t_poll_retx.parse(root["ul_am"])) { ERROR("Error can't find t_poll_retx in section ul_am"); @@ -683,6 +691,14 @@ int field_5g_srb::parse(libconfig::Setting& root) if (root.exists("dl_am")) { asn1::rrc_nr::dl_am_rlc_s& dl_am_rlc = rlc_cfg->am().dl_am_rlc; + // SN length + field_asn1_enum_number rlc_sn_size_ul("sn_field_len", &dl_am_rlc.sn_field_len); + if (rlc_sn_size_ul.parse(root["dl_am"]) == SRSRAN_ERROR) { + dl_am_rlc.sn_field_len_present = false; + } else { + dl_am_rlc.sn_field_len_present = true; + } + field_asn1_enum_number t_reassembly("t_reassembly", &dl_am_rlc.t_reassembly); if (t_reassembly.parse(root["dl_am"])) { ERROR("Error can't find t_reordering in section dl_am"); From 42011401ff2c390b63636f0c5ade37e88e592ede Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Fri, 29 Apr 2022 18:19:45 +0100 Subject: [PATCH 22/25] gnb,config: remove SN length for 4G SRB configs --- srsenb/rb.conf.example | 3 --- 1 file changed, 3 deletions(-) diff --git a/srsenb/rb.conf.example b/srsenb/rb.conf.example index dc19c63f3..9a5bc5d44 100644 --- a/srsenb/rb.conf.example +++ b/srsenb/rb.conf.example @@ -5,14 +5,12 @@ // srb1_config = { // rlc_config = { // ul_am = { -// sn_field_len = 12; // t_poll_retx = 45; // poll_pdu = -1; // poll_byte = -1; // max_retx_thresh = 4; // }; // dl_am = { -// sn_field_len = 12; // t_reassembly = 50; // t_status_prohibit = 50; // }; @@ -22,7 +20,6 @@ // srb2_config = { // rlc_config = { // ul_am = { -// sn_field_len = 12; // t_poll_retx = 45; // poll_pdu = -1; // poll_byte = -1; From 97b32f2fb69a9e55544844f62f5676efc2f531bc Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Wed, 11 May 2022 15:27:35 +0100 Subject: [PATCH 23/25] rrc_nr,utils: fix generation of RLC configs from ASN.1 structs created from cfg parser. --- lib/src/asn1/rrc_nr_utils.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/src/asn1/rrc_nr_utils.cc b/lib/src/asn1/rrc_nr_utils.cc index 62c23adaa..2eeba8f65 100644 --- a/lib/src/asn1/rrc_nr_utils.cc +++ b/lib/src/asn1/rrc_nr_utils.cc @@ -125,6 +125,12 @@ int make_rlc_config_t(const rlc_cfg_c& asn1_type, uint8_t bearer_id, rlc_config_ default: break; } + rlc_cfg.am_nr.t_poll_retx = asn1_type.am().ul_am_rlc.t_poll_retx.to_number(); + rlc_cfg.am_nr.poll_pdu = asn1_type.am().ul_am_rlc.poll_pdu.to_number(); + rlc_cfg.am_nr.poll_byte = asn1_type.am().ul_am_rlc.poll_byte.to_number(); + rlc_cfg.am_nr.max_retx_thresh = asn1_type.am().ul_am_rlc.max_retx_thres.to_number(); + rlc_cfg.am_nr.t_reassembly = asn1_type.am().dl_am_rlc.t_reassembly.to_number(); + rlc_cfg.am_nr.t_status_prohibit = asn1_type.am().dl_am_rlc.t_status_prohibit.to_number(); break; case rlc_cfg_c::types_opts::um_bi_dir: rlc_cfg = rlc_config_t::default_rlc_um_nr_config(); @@ -148,6 +154,7 @@ int make_rlc_config_t(const rlc_cfg_c& asn1_type, uint8_t bearer_id, rlc_config_ default: break; } + rlc_cfg.um_nr.t_reassembly_ms = asn1_type.um_bi_dir().dl_um_rlc.t_reassembly.to_number(); break; case rlc_cfg_c::types_opts::um_uni_dir_dl: asn1::log_warning("NR RLC type %s is not supported", asn1_type.type().to_string()); From a72279dcbd14b4e5e8ad55301a73dfc0f197dd30 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Wed, 11 May 2022 16:09:07 +0100 Subject: [PATCH 24/25] Addressed review comments: 1 - enb,cfg: fix typo 2 - enb,config: added back deleted config option 3 - ue,rrc_nr: fix wrong log level in log message 4 - enb,config: remove unused parameter --- srsenb/rb.conf.example | 12 +++++++++--- srsenb/src/enb_cfg_parser.cc | 2 +- srsgnb/hdr/stack/rrc/rrc_nr_config.h | 3 +-- srsue/src/stack/rrc_nr/rrc_nr.cc | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/srsenb/rb.conf.example b/srsenb/rb.conf.example index 9a5bc5d44..b22e14549 100644 --- a/srsenb/rb.conf.example +++ b/srsenb/rb.conf.example @@ -10,9 +10,12 @@ // poll_byte = -1; // max_retx_thresh = 4; // }; -// dl_am = { -// t_reassembly = 50; -// t_status_prohibit = 50; +// dl_am = { +// t_reordering = 35; +// t_status_prohibit = 0; +// }; +// enb_specific = { +// dl_max_retx_thresh = 32; // }; // }; // } @@ -29,6 +32,9 @@ // t_reordering = 35; // t_status_prohibit = 0; // }; +// enb_specific = { +// dl_max_retx_thresh = 32; +// }; // }; // } diff --git a/srsenb/src/enb_cfg_parser.cc b/srsenb/src/enb_cfg_parser.cc index 9f4dd845e..0b627efe4 100644 --- a/srsenb/src/enb_cfg_parser.cc +++ b/srsenb/src/enb_cfg_parser.cc @@ -644,7 +644,7 @@ int field_5g_srb::parse(libconfig::Setting& root) cfg.present = true; } - // RLC-UM Should not exist section + // RLC-UM must not exist in this section if (root.exists("ul_um") || root.exists("dl_um")) { ERROR("Error SRBs must be AM."); return SRSRAN_ERROR; diff --git a/srsgnb/hdr/stack/rrc/rrc_nr_config.h b/srsgnb/hdr/stack/rrc/rrc_nr_config.h index 0b63e98db..74469b535 100644 --- a/srsgnb/hdr/stack/rrc/rrc_nr_config.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr_config.h @@ -46,8 +46,7 @@ struct rrc_cell_cfg_nr_t { typedef std::vector rrc_cell_list_nr_t; struct srb_5g_cfg_t { - int enb_dl_max_retx_thres = -1; - bool present = false; + bool present = false; asn1::rrc_nr::rlc_cfg_c rlc_cfg; }; diff --git a/srsue/src/stack/rrc_nr/rrc_nr.cc b/srsue/src/stack/rrc_nr/rrc_nr.cc index 9a57422e5..31bb4709d 100644 --- a/srsue/src/stack/rrc_nr/rrc_nr.cc +++ b/srsue/src/stack/rrc_nr/rrc_nr.cc @@ -1062,7 +1062,7 @@ bool rrc_nr::apply_rlc_add_mod(const rlc_bearer_cfg_s& rlc_bearer_cfg) return false; } } else if (not is_drb) { - logger.error("Using default RLC configs for SRB%d", srb_id); + logger.debug("Using default RLC configs for SRB%d", srb_id); rlc_cfg = rlc_config_t::default_rlc_am_nr_config(); } else { logger.error("In RLC bearer cfg does not contain rlc cfg"); From 9687af766089e11005bdb50c53d37f6809409b2b Mon Sep 17 00:00:00 2001 From: Robert Falkenberg Date: Thu, 12 May 2022 06:42:15 +0200 Subject: [PATCH 25/25] add missing include for build with GCC 12 Related info: https://gcc.gnu.org/gcc-12/porting_to.html --- lib/include/srsran/srslog/bundled/fmt/core.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/include/srsran/srslog/bundled/fmt/core.h b/lib/include/srsran/srslog/bundled/fmt/core.h index d676f27e5..04a85f295 100644 --- a/lib/include/srsran/srslog/bundled/fmt/core.h +++ b/lib/include/srsran/srslog/bundled/fmt/core.h @@ -8,7 +8,8 @@ #ifndef FMT_CORE_H_ #define FMT_CORE_H_ -#include // std::FILE +#include +#include // std::FILE #include #include #include