From b20f7ba541541cc26c58f0c7dfac532da64fbaff Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Wed, 23 Oct 2019 11:53:00 +0200 Subject: [PATCH] refactor RLC UM and add NR receiver --- .../srslte/interfaces/rrc_interface_types.h | 28 +- lib/include/srslte/upper/rlc_am_lte.h | 5 +- lib/include/srslte/upper/rlc_common.h | 15 +- lib/include/srslte/upper/rlc_metrics.h | 22 +- lib/include/srslte/upper/rlc_tm.h | 6 +- lib/include/srslte/upper/rlc_um.h | 281 ---- lib/include/srslte/upper/rlc_um_base.h | 165 +++ lib/include/srslte/upper/rlc_um_lte.h | 135 ++ lib/include/srslte/upper/rlc_um_nr.h | 143 ++ lib/src/upper/CMakeLists.txt | 12 +- lib/src/upper/rlc.cc | 62 +- lib/src/upper/rlc_am_lte.cc | 9 +- lib/src/upper/rlc_tm.cc | 16 +- lib/src/upper/rlc_um.cc | 1313 ----------------- lib/src/upper/rlc_um_base.cc | 285 ++++ lib/src/upper/rlc_um_lte.cc | 815 ++++++++++ lib/src/upper/rlc_um_nr.cc | 688 +++++++++ lib/test/upper/CMakeLists.txt | 1 + lib/test/upper/rlc_am_test.cc | 30 +- lib/test/upper/rlc_stress_test.cc | 125 +- lib/test/upper/rlc_test_common.h | 13 + lib/test/upper/rlc_um_data_test.cc | 4 +- lib/test/upper/rlc_um_nr_pdu_test.cc | 4 +- lib/test/upper/rlc_um_nr_test.cc | 525 ++++++- lib/test/upper/rlc_um_test.cc | 268 ++-- 25 files changed, 3041 insertions(+), 1929 deletions(-) delete mode 100644 lib/include/srslte/upper/rlc_um.h create mode 100644 lib/include/srslte/upper/rlc_um_base.h create mode 100644 lib/include/srslte/upper/rlc_um_lte.h create mode 100644 lib/include/srslte/upper/rlc_um_nr.h delete mode 100644 lib/src/upper/rlc_um.cc create mode 100644 lib/src/upper/rlc_um_base.cc create mode 100644 lib/src/upper/rlc_um_lte.cc create mode 100644 lib/src/upper/rlc_um_nr.cc diff --git a/lib/include/srslte/interfaces/rrc_interface_types.h b/lib/include/srslte/interfaces/rrc_interface_types.h index cc90c107a..67761d461 100644 --- a/lib/include/srslte/interfaces/rrc_interface_types.h +++ b/lib/include/srslte/interfaces/rrc_interface_types.h @@ -257,29 +257,30 @@ struct rlc_um_nr_config_t { rlc_um_nr_sn_size_t sn_field_length; // Number of bits used for sequence number uint32_t UM_Window_Size; uint32_t mod; // Rx/Tx counter modulus + int32_t t_reassembly_ms; // Timer used by rx to detect PDU loss (ms) }; #define RLC_TX_QUEUE_LEN (128) -enum class rlc_type_t { lte, nr, nulltype }; -inline std::string to_string(const rlc_type_t& type) +enum class srslte_rat_t { lte, nr, nulltype }; +inline std::string to_string(const srslte_rat_t& type) { constexpr static const char* options[] = {"LTE", "NR"}; - return enum_to_text(options, (uint32_t)rlc_type_t::nulltype, (uint32_t)type); + return enum_to_text(options, (uint32_t)srslte_rat_t::nulltype, (uint32_t)type); } class rlc_config_t { public: - rlc_type_t type; - rlc_mode_t rlc_mode; - rlc_am_config_t am; - rlc_um_config_t um; + srslte_rat_t rat; + rlc_mode_t rlc_mode; + rlc_am_config_t am; + rlc_um_config_t um; rlc_um_nr_config_t um_nr; uint32_t tx_queue_length; rlc_config_t() : - type(rlc_type_t::lte), + rat(srslte_rat_t::lte), rlc_mode(rlc_mode_t::tm), am(), um(), @@ -290,7 +291,7 @@ public: static rlc_config_t mch_config() { rlc_config_t cfg = {}; - cfg.type = rlc_type_t::lte; + cfg.rat = srslte_rat_t::lte; cfg.rlc_mode = rlc_mode_t::um; cfg.um.t_reordering = 45; cfg.um.rx_sn_field_length = rlc_umd_sn_size_t::size5bits; @@ -309,7 +310,7 @@ public: } // SRB1 and SRB2 are AM rlc_config_t rlc_cfg = {}; - rlc_cfg.type = rlc_type_t::lte; + rlc_cfg.rat = srslte_rat_t::lte; rlc_cfg.rlc_mode = rlc_mode_t::am; rlc_cfg.am.t_poll_retx = 45; rlc_cfg.am.poll_pdu = -1; @@ -322,7 +323,7 @@ public: static rlc_config_t default_rlc_um_config(uint32_t sn_size = 10) { rlc_config_t cnfg = {}; - cnfg.type = rlc_type_t::lte; + cnfg.rat = srslte_rat_t::lte; cnfg.rlc_mode = rlc_mode_t::um; cnfg.um.t_reordering = 5; if (sn_size == 10) { @@ -345,7 +346,7 @@ public: static rlc_config_t default_rlc_am_config() { rlc_config_t rlc_cnfg = {}; - rlc_cnfg.type = rlc_type_t::lte; + rlc_cnfg.rat = srslte_rat_t::lte; rlc_cnfg.rlc_mode = rlc_mode_t::am; rlc_cnfg.am.t_reordering = 5; rlc_cnfg.am.t_status_prohibit = 5; @@ -358,7 +359,7 @@ public: static rlc_config_t default_rlc_um_nr_config(uint32_t sn_size = 6) { rlc_config_t cnfg = {}; - cnfg.type = rlc_type_t::nr; + cnfg.rat = srslte_rat_t::nr; cnfg.rlc_mode = rlc_mode_t::um; if (sn_size == 6) { cnfg.um_nr.sn_field_length = rlc_um_nr_sn_size_t::size6bits; @@ -371,6 +372,7 @@ public: } else { return {}; } + cnfg.um_nr.t_reassembly_ms = 5; // lowest non-zero value return cnfg; } }; diff --git a/lib/include/srslte/upper/rlc_am_lte.h b/lib/include/srslte/upper/rlc_am_lte.h index 183199a13..15de3b097 100644 --- a/lib/include/srslte/upper/rlc_am_lte.h +++ b/lib/include/srslte/upper/rlc_am_lte.h @@ -86,8 +86,7 @@ public: int read_pdu(uint8_t *payload, uint32_t nof_bytes); void write_pdu(uint8_t *payload, uint32_t nof_bytes); - uint32_t get_num_tx_bytes(); - uint32_t get_num_rx_bytes(); + rlc_bearer_metrics_t get_metrics(); void reset_metrics(); private: @@ -283,6 +282,8 @@ private: // Rx and Tx objects rlc_am_lte_tx tx; rlc_am_lte_rx rx; + + rlc_bearer_metrics_t metrics = {}; }; /**************************************************************************** diff --git a/lib/include/srslte/upper/rlc_common.h b/lib/include/srslte/upper/rlc_common.h index 1b3875552..ede2dabf9 100644 --- a/lib/include/srslte/upper/rlc_common.h +++ b/lib/include/srslte/upper/rlc_common.h @@ -23,6 +23,7 @@ #define SRSLTE_RLC_COMMON_H #include "srslte/common/block_queue.h" +#include "srslte/upper/rlc_metrics.h" #include namespace srslte { @@ -64,6 +65,12 @@ inline std::string to_string(const rlc_nr_si_field_t& si) return enum_to_text(options, (uint32_t)rlc_nr_si_field_t::nulltype, (uint32_t)si); } +inline std::string to_string_short(const rlc_nr_si_field_t& si) +{ + constexpr static const char* options[] = {"full", "first", "last", "middle"}; + return enum_to_text(options, (uint32_t)rlc_nr_si_field_t::nulltype, (uint32_t)si); +} + static inline uint8_t operator&(rlc_nr_si_field_t lhs, int rhs) { return static_cast(static_cast::type>(lhs) & @@ -91,7 +98,7 @@ typedef struct { rlc_nr_si_field_t si; // Segmentation info rlc_um_nr_sn_size_t sn_size; // Sequence number size (6 or 12 bits) uint16_t sn; // Sequence number - uint16_t so; // Sequence offset + uint16_t so; // Segment offset } rlc_um_nr_pdu_header_t; // AMD PDU Header @@ -137,8 +144,9 @@ struct rlc_amd_pdu_header_t{ lsf = h.lsf; so = h.so; N_li = h.N_li; - for(uint32_t i=0;i -#include -#include -#include - -namespace srslte { - -struct rlc_umd_pdu_t{ - rlc_umd_pdu_header_t header; - unique_byte_buffer_t buf; -}; - -typedef struct { - rlc_um_nr_pdu_header_t header; - unique_byte_buffer_t buf; -} rlc_umd_pdu_nr_t; - -class rlc_um - :public rlc_common -{ -public: - rlc_um(srslte::log* log_, - uint32_t lcid_, - srsue::pdcp_interface_rlc* pdcp_, - srsue::rrc_interface_rlc* rrc_, - srslte::timer_handler* timers_); - ~rlc_um(); - bool configure(rlc_config_t cnfg); - void reestablish(); - void stop(); - void empty_queue(); - bool is_mrb(); - - rlc_mode_t get_mode(); - uint32_t get_bearer(); - - // PDCP interface - void write_sdu(unique_byte_buffer_t sdu, bool blocking = true); - - // MAC interface - bool has_data(); - uint32_t get_buffer_state(); - int read_pdu(uint8_t *payload, uint32_t nof_bytes); - void write_pdu(uint8_t *payload, uint32_t nof_bytes); - int get_increment_sequence_num(); - - uint32_t get_num_tx_bytes(); - uint32_t get_num_rx_bytes(); - void reset_metrics(); - -private: - // Transmitter sub-class base - class rlc_um_tx_base - { - public: - rlc_um_tx_base(srslte::log* log_); - virtual ~rlc_um_tx_base(); - virtual bool configure(rlc_config_t cfg, std::string rb_name) = 0; - int build_data_pdu(uint8_t *payload, uint32_t nof_bytes); - void stop(); - void reestablish(); - void empty_queue(); - void write_sdu(unique_byte_buffer_t sdu); - void try_write_sdu(unique_byte_buffer_t sdu); - uint32_t get_num_tx_bytes(); - void reset_metrics(); - bool has_data(); - uint32_t get_buffer_state(); - - protected: - byte_buffer_pool* pool = nullptr; - srslte::log* log = nullptr; - std::string rb_name; - - /**************************************************************************** - * Configurable parameters - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ - rlc_config_t cfg = {}; - - // TX SDU buffers - rlc_tx_queue tx_sdu_queue; - unique_byte_buffer_t tx_sdu; - - // Mutexes - std::mutex mutex; - - bool tx_enabled = false; - - uint32_t num_tx_bytes = 0; - - virtual int build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes) = 0; - - // helper functions - virtual void debug_state() = 0; - const char* get_rb_name(); - }; - - // Transmitter sub-class for LTE - class rlc_um_tx : public rlc_um_tx_base - { - public: - rlc_um_tx(srslte::log* log_); - - bool configure(rlc_config_t cfg, std::string rb_name); - int build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes); - - private: - /**************************************************************************** - * State variables and counters - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ - uint32_t vt_us = 0; // Send state. SN to be assigned for next PDU. - - void debug_state(); - }; - - // Transmitter sub-class for NR - class rlc_um_tx_nr : public rlc_um_tx_base - { - public: - rlc_um_tx_nr(srslte::log* log_); - - bool configure(rlc_config_t cfg, std::string rb_name); - int build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes); - - private: - uint32_t TX_Next = 0; // send state as defined in TS 38.322 v15.3 Section 7 - - void debug_state(); - }; - - // Receiver sub-class - class rlc_um_rx : public timer_callback { - public: - rlc_um_rx(srslte::log* log_, - uint32_t lcid_, - srsue::pdcp_interface_rlc* pdcp_, - srsue::rrc_interface_rlc* rrc_, - srslte::timer_handler* timers_); - ~rlc_um_rx(); - void stop(); - void reestablish(); - bool configure(rlc_config_t cfg, std::string rb_name); - void handle_data_pdu(uint8_t *payload, uint32_t nof_bytes); - void reassemble_rx_sdus(); - bool pdu_belongs_to_rx_sdu(); - bool inside_reordering_window(uint16_t sn); - uint32_t get_num_rx_bytes(); - void reset_metrics(); - - // Timeout callback interface - void timer_expired(uint32_t timeout_id); - - private: - void reset(); - - byte_buffer_pool* pool = nullptr; - srslte::log* log = nullptr; - srslte::timer_handler* timers = nullptr; - std::string rb_name; - - /**************************************************************************** - * Configurable parameters - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ - rlc_config_t cfg = {}; - - // Rx window - std::map rx_window; - - // RX SDU buffers - unique_byte_buffer_t rx_sdu; - uint32_t vr_ur_in_rx_sdu = 0; - - // Rx state variables and counter - uint32_t vr_ur = 0; // Receive state. SN of earliest PDU still considered for reordering. - uint32_t vr_ux = 0; // t_reordering state. SN following PDU which triggered t_reordering. - uint32_t vr_uh = 0; // Highest rx state. SN following PDU with highest SN among rxed PDUs. - bool pdu_lost = false; - - uint32_t num_rx_bytes = 0; - - // Upper layer handles and variables - srsue::pdcp_interface_rlc* pdcp = nullptr; - srsue::rrc_interface_rlc* rrc = nullptr; - uint32_t lcid = 0; - - // Mutexes - std::mutex mutex; - - bool rx_enabled = false; - - /**************************************************************************** - * Timers - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ - srslte::timer_handler::unique_timer reordering_timer; - - // helper functions - void debug_state(); - const char* get_rb_name(); - }; - - // Common variables needed by parent class - srsue::rrc_interface_rlc* rrc = nullptr; - srslte::log* log = nullptr; - uint32_t lcid = 0; - rlc_config_t cfg = {}; - std::string rb_name; - byte_buffer_pool* pool = nullptr; - - std::string get_rb_name(srsue::rrc_interface_rlc *rrc, uint32_t lcid, bool is_mrb); - - // Rx and Tx objects - std::unique_ptr tx; - rlc_um_rx rx; -}; - -/**************************************************************************** - * Header pack/unpack helper functions - * Ref: 3GPP TS 36.322 v10.0.0 Section 6.2.1 - ***************************************************************************/ -void rlc_um_read_data_pdu_header(byte_buffer_t* pdu, rlc_umd_sn_size_t sn_size, rlc_umd_pdu_header_t* header); -void rlc_um_read_data_pdu_header(uint8_t *payload, uint32_t nof_bytes, rlc_umd_sn_size_t sn_size, rlc_umd_pdu_header_t *header); -void rlc_um_write_data_pdu_header(rlc_umd_pdu_header_t* header, byte_buffer_t* pdu); - -uint32_t rlc_um_packed_length(rlc_umd_pdu_header_t *header); -bool rlc_um_start_aligned(uint8_t fi); -bool rlc_um_end_aligned(uint8_t fi); - -/**************************************************************************** - * Header pack/unpack helper functions for NR - * Ref: 3GPP TS 38.322 v15.3.0 Section 6.2.2.3 - ***************************************************************************/ -uint32_t rlc_um_nr_read_data_pdu_header(const byte_buffer_t* pdu, - const rlc_um_nr_sn_size_t sn_size, - rlc_um_nr_pdu_header_t* header); - -uint32_t rlc_um_nr_read_data_pdu_header(const uint8_t* payload, - const uint32_t nof_bytes, - const rlc_um_nr_sn_size_t sn_size, - rlc_um_nr_pdu_header_t* header); - -uint32_t rlc_um_nr_write_data_pdu_header(const rlc_um_nr_pdu_header_t& header, byte_buffer_t* pdu); - -uint32_t rlc_um_nr_packed_length(const rlc_um_nr_pdu_header_t& header); - -} // namespace srslte - -#endif // SRSLTE_RLC_UM_H diff --git a/lib/include/srslte/upper/rlc_um_base.h b/lib/include/srslte/upper/rlc_um_base.h new file mode 100644 index 000000000..4ff633fe4 --- /dev/null +++ b/lib/include/srslte/upper/rlc_um_base.h @@ -0,0 +1,165 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#ifndef SRSLTE_RLC_UM_BASE_H +#define SRSLTE_RLC_UM_BASE_H + +#include "srslte/common/buffer_pool.h" +#include "srslte/common/common.h" +#include "srslte/common/log.h" +#include "srslte/interfaces/ue_interfaces.h" +#include "srslte/upper/rlc_common.h" +#include "srslte/upper/rlc_tx_queue.h" +#include +#include +#include +#include + +namespace srslte { + +class rlc_um_base : public rlc_common +{ +public: + rlc_um_base(srslte::log* log_, + uint32_t lcid_, + srsue::pdcp_interface_rlc* pdcp_, + srsue::rrc_interface_rlc* rrc_, + srslte::timer_handler* timers_); + virtual ~rlc_um_base(); + void reestablish(); + void stop(); + void empty_queue(); + bool is_mrb(); + + rlc_mode_t get_mode(); + uint32_t get_bearer(); + + // PDCP interface + void write_sdu(unique_byte_buffer_t sdu, bool blocking = true); + + // MAC interface + bool has_data(); + uint32_t get_buffer_state(); + int read_pdu(uint8_t* payload, uint32_t nof_bytes); + void write_pdu(uint8_t* payload, uint32_t nof_bytes); + int get_increment_sequence_num(); + + rlc_bearer_metrics_t get_metrics(); + void reset_metrics(); + +protected: + // Transmitter sub-class base + class rlc_um_base_tx + { + public: + rlc_um_base_tx(rlc_um_base* parent_); + virtual ~rlc_um_base_tx(); + virtual bool configure(rlc_config_t cfg, std::string rb_name) = 0; + int build_data_pdu(uint8_t* payload, uint32_t nof_bytes); + void stop(); + void reestablish(); + void empty_queue(); + void write_sdu(unique_byte_buffer_t sdu); + void try_write_sdu(unique_byte_buffer_t sdu); + void reset_metrics(); + bool has_data(); + virtual uint32_t get_buffer_state() = 0; + + protected: + byte_buffer_pool* pool = nullptr; + srslte::log* log = nullptr; + std::string rb_name; + + rlc_config_t cfg = {}; + + // TX SDU buffers + rlc_tx_queue tx_sdu_queue; + unique_byte_buffer_t tx_sdu; + + // Mutexes + std::mutex mutex; + + virtual int build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes) = 0; + + // helper functions + virtual void debug_state() = 0; + }; + + // Receiver sub-class base + class rlc_um_base_rx : public timer_callback + { + + public: + rlc_um_base_rx(rlc_um_base* parent_); + virtual ~rlc_um_base_rx(); + virtual bool configure() = 0; + + virtual void stop() = 0; + virtual void reestablish() = 0; + + virtual void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) = 0; + + protected: + byte_buffer_pool* pool = nullptr; + srslte::log* log = nullptr; + srslte::timer_handler* timers = nullptr; + srsue::pdcp_interface_rlc* pdcp = nullptr; + srsue::rrc_interface_rlc* rrc = nullptr; + + rlc_bearer_metrics_t& metrics; + + std::string rb_name; + rlc_config_t cfg = {}; + + unique_byte_buffer_t rx_sdu; + + uint32_t& lcid; + + std::mutex mutex; + + // helper functions + virtual void debug_state() = 0; + }; + + // Common variables needed by parent class + srsue::rrc_interface_rlc* rrc = nullptr; + srsue::pdcp_interface_rlc* pdcp = nullptr; + srslte::log* log = nullptr; + srslte::timer_handler* timers = nullptr; + uint32_t lcid = 0; + rlc_config_t cfg = {}; + std::string rb_name; + byte_buffer_pool* pool = nullptr; + std::string get_rb_name(srsue::rrc_interface_rlc* rrc, uint32_t lcid, bool is_mrb); + + // Rx and Tx objects + std::unique_ptr tx; + std::unique_ptr rx; + + bool tx_enabled = false; + bool rx_enabled = false; + + rlc_bearer_metrics_t metrics = {}; +}; + +} // namespace srslte + +#endif // SRSLTE_RLC_UM_BASE_H diff --git a/lib/include/srslte/upper/rlc_um_lte.h b/lib/include/srslte/upper/rlc_um_lte.h new file mode 100644 index 000000000..89c32c9f5 --- /dev/null +++ b/lib/include/srslte/upper/rlc_um_lte.h @@ -0,0 +1,135 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#ifndef SRSLTE_RLC_UM_LTE_H +#define SRSLTE_RLC_UM_LTE_H + +#include "srslte/common/buffer_pool.h" +#include "srslte/common/common.h" +#include "srslte/common/log.h" +#include "srslte/interfaces/ue_interfaces.h" +#include "srslte/upper/rlc_tx_queue.h" +#include "srslte/upper/rlc_um_base.h" +#include +#include +#include +#include + +namespace srslte { + +typedef struct { + rlc_umd_pdu_header_t header; + unique_byte_buffer_t buf; +} rlc_umd_pdu_t; + +class rlc_um_lte : public rlc_um_base +{ +public: + rlc_um_lte(srslte::log* log_, + uint32_t lcid_, + srsue::pdcp_interface_rlc* pdcp_, + srsue::rrc_interface_rlc* rrc_, + srslte::timer_handler* timers_); + ~rlc_um_lte(); + bool configure(rlc_config_t cnfg); + +private: + // Transmitter sub-class for LTE + class rlc_um_lte_tx : public rlc_um_base_tx + { + public: + rlc_um_lte_tx(rlc_um_base* parent_); + + bool configure(rlc_config_t cfg, std::string rb_name); + int build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes); + uint32_t get_buffer_state(); + + private: + /**************************************************************************** + * State variables and counters + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ + uint32_t vt_us = 0; // Send state. SN to be assigned for next PDU. + + void debug_state(); + }; + + // Receiver sub-class for LTE + class rlc_um_lte_rx : public rlc_um_base_rx + { + public: + rlc_um_lte_rx(rlc_um_base* parent_); + ~rlc_um_lte_rx(); + void stop(); + void reestablish(); + bool configure(); + void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes); + void reassemble_rx_sdus(); + bool pdu_belongs_to_rx_sdu(); + bool inside_reordering_window(uint16_t sn); + + // Timeout callback interface + void timer_expired(uint32_t timeout_id); + + private: + void reset(); + + // Rx window + std::map rx_window; + + // RX SDU buffers + uint32_t vr_ur_in_rx_sdu = 0; + + // Rx state variables and counter + uint32_t vr_ur = 0; // Receive state. SN of earliest PDU still considered for reordering. + uint32_t vr_ux = 0; // t_reordering state. SN following PDU which triggered t_reordering. + uint32_t vr_uh = 0; // Highest rx state. SN following PDU with highest SN among rxed PDUs. + bool pdu_lost = false; + + /**************************************************************************** + * Timers + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ + srslte::timer_handler::unique_timer reordering_timer; + + // helper functions + void debug_state(); + }; +}; + +/**************************************************************************** + * Header pack/unpack helper functions + * Ref: 3GPP TS 36.322 v10.0.0 Section 6.2.1 + ***************************************************************************/ +void rlc_um_read_data_pdu_header(byte_buffer_t* pdu, rlc_umd_sn_size_t sn_size, rlc_umd_pdu_header_t* header); +void rlc_um_read_data_pdu_header(uint8_t* payload, + uint32_t nof_bytes, + rlc_umd_sn_size_t sn_size, + rlc_umd_pdu_header_t* header); +void rlc_um_write_data_pdu_header(rlc_umd_pdu_header_t* header, byte_buffer_t* pdu); + +uint32_t rlc_um_packed_length(rlc_umd_pdu_header_t* header); +bool rlc_um_start_aligned(uint8_t fi); +bool rlc_um_end_aligned(uint8_t fi); + +} // namespace srslte + +#endif // SRSLTE_RLC_UM_LTE_H diff --git a/lib/include/srslte/upper/rlc_um_nr.h b/lib/include/srslte/upper/rlc_um_nr.h new file mode 100644 index 000000000..0fb2153fa --- /dev/null +++ b/lib/include/srslte/upper/rlc_um_nr.h @@ -0,0 +1,143 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#ifndef SRSLTE_RLC_UM_NR_H +#define SRSLTE_RLC_UM_NR_H + +#include "srslte/common/buffer_pool.h" +#include "srslte/common/common.h" +#include "srslte/common/log.h" +#include "srslte/interfaces/ue_interfaces.h" +#include "srslte/upper/rlc_tx_queue.h" +#include "srslte/upper/rlc_um_base.h" +#include +#include +#include +#include + +namespace srslte { + +typedef struct { + rlc_um_nr_pdu_header_t header; + unique_byte_buffer_t buf; +} rlc_umd_pdu_nr_t; + +class rlc_um_nr : public rlc_um_base +{ +public: + rlc_um_nr(srslte::log* log_, + uint32_t lcid_, + srsue::pdcp_interface_rlc* pdcp_, + srsue::rrc_interface_rlc* rrc_, + srslte::timer_handler* timers_); + ~rlc_um_nr(); + bool configure(rlc_config_t cnfg); + +private: + // Transmitter sub-class for NR + class rlc_um_nr_tx : public rlc_um_base_tx + { + public: + rlc_um_nr_tx(rlc_um_base* parent_); + + bool configure(rlc_config_t cfg, std::string rb_name); + int build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes); + uint32_t get_buffer_state(); + + private: + uint32_t TX_Next = 0; // send state as defined in TS 38.322 v15.3 Section 7 + // It holds the value of the SN to be assigned for the next newly generated UMD PDU with + // segment. It is initially set to 0, and is updated after the UM RLC entity submits a UMD PDU + // including the last segment of an RLC SDU to lower layers. + + uint32_t next_so = 0; // The segment offset for the next generated PDU + + void debug_state(); + }; + + // Receiver sub-class for NR + class rlc_um_nr_rx : public rlc_um_base_rx + { + public: + rlc_um_nr_rx(rlc_um_base* parent_); + + bool configure(); + void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes); + + void reestablish(); + void stop(); + + void timer_expired(uint32_t timeout_id); + + private: + bool sn_in_reassembly_window(const uint32_t sn); + bool sn_invalid_for_rx_buffer(const uint32_t sn); + bool has_missing_byte_segment(const uint32_t sn); + unique_byte_buffer_t + rlc_um_nr_strip_pdu_header(const rlc_um_nr_pdu_header_t& header, const uint8_t* payload, const uint32_t nof_bytes); + + void handle_rx_buffer_update(const uint32_t sn); + + uint32_t RX_Next_Reassembly = 0; // the earliest SN that is still considered for reassembly + uint32_t RX_Timer_Trigger = 0; // the SN following the SN which triggered t-Reassembly + uint32_t RX_Next_Highest = 0; // the SN following the SN of the UMD PDU with the highest SN among + // received UMD PDUs. It serves as the higher edge of the reassembly window. + + // Rx window + typedef struct { + std::map segments; // Map of segments with SO as key + unique_byte_buffer_t sdu; + uint32_t next_expected_so; + uint32_t total_sdu_length; + } rlc_umd_pdu_segments_nr_t; + std::map rx_window; + + void update_total_sdu_length(rlc_umd_pdu_segments_nr_t& pdu_segments, const rlc_umd_pdu_nr_t& rx_pdu); + + // TS 38.322 Sec. 7.3 + srslte::timer_handler::unique_timer reassembly_timer; // to detect loss of RLC PDUs at lower layers + + // helper functions + void reset(); + void debug_state(); + }; +}; + +/**************************************************************************** + * Header pack/unpack helper functions for NR + * Ref: 3GPP TS 38.322 v15.3.0 Section 6.2.2.3 + ***************************************************************************/ +uint32_t rlc_um_nr_read_data_pdu_header(const byte_buffer_t* pdu, + const rlc_um_nr_sn_size_t sn_size, + rlc_um_nr_pdu_header_t* header); + +uint32_t rlc_um_nr_read_data_pdu_header(const uint8_t* payload, + const uint32_t nof_bytes, + const rlc_um_nr_sn_size_t sn_size, + rlc_um_nr_pdu_header_t* header); + +uint32_t rlc_um_nr_write_data_pdu_header(const rlc_um_nr_pdu_header_t& header, byte_buffer_t* pdu); + +uint32_t rlc_um_nr_packed_length(const rlc_um_nr_pdu_header_t& header); + +} // namespace srslte + +#endif // SRSLTE_RLC_UM_NR_H diff --git a/lib/src/upper/CMakeLists.txt b/lib/src/upper/CMakeLists.txt index 1c801c930..364b1ceb4 100644 --- a/lib/src/upper/CMakeLists.txt +++ b/lib/src/upper/CMakeLists.txt @@ -18,7 +18,17 @@ # and at http://www.gnu.org/licenses/. # -set(SOURCES gtpu.cc pdcp.cc pdcp_entity_base.cc pdcp_entity_lte.cc pdcp_entity_nr.cc rlc.cc rlc_am_lte.cc rlc_um.cc rlc_tm.cc) +set(SOURCES gtpu.cc + pdcp.cc + pdcp_entity_base.cc + pdcp_entity_lte.cc + pdcp_entity_nr.cc + rlc.cc + rlc_tm.cc + rlc_um_base.cc + rlc_um_lte.cc + rlc_um_nr.cc + rlc_am_lte.cc) add_library(srslte_upper STATIC ${SOURCES}) target_link_libraries(srslte_upper srslte_common srslte_asn1) install(TARGETS srslte_upper DESTINATION ${LIBRARY_DIR}) \ No newline at end of file diff --git a/lib/src/upper/rlc.cc b/lib/src/upper/rlc.cc index dab5df09d..1cdf7a3bc 100644 --- a/lib/src/upper/rlc.cc +++ b/lib/src/upper/rlc.cc @@ -22,7 +22,8 @@ #include "srslte/upper/rlc.h" #include "srslte/upper/rlc_am_lte.h" #include "srslte/upper/rlc_tm.h" -#include "srslte/upper/rlc_um.h" +#include "srslte/upper/rlc_um_lte.h" +#include "srslte/upper/rlc_um_nr.h" namespace srslte { @@ -100,20 +101,21 @@ void rlc::get_metrics(rlc_metrics_t &m) double secs = (double)metrics_time[0].tv_sec + metrics_time[0].tv_usec*1e-6; for (rlc_map_t::iterator it = rlc_array.begin(); it != rlc_array.end(); ++it) { - m.dl_tput_mbps[it->first] = (it->second->get_num_rx_bytes()*8/static_cast(1e6))/secs; - m.ul_tput_mbps[it->first] = (it->second->get_num_tx_bytes()*8/static_cast(1e6))/secs; + rlc_bearer_metrics_t metrics = it->second->get_metrics(); rlc_log->info("LCID=%d, RX throughput: %4.6f Mbps. TX throughput: %4.6f Mbps.\n", - it->first, - (it->second->get_num_rx_bytes()*8/static_cast(1e6))/secs, - (it->second->get_num_tx_bytes()*8/static_cast(1e6))/secs); + it->first, + (metrics.num_rx_bytes * 8 / static_cast(1e6)) / secs, + (metrics.num_tx_bytes * 8 / static_cast(1e6)) / secs); + m.bearer[it->first] = metrics; } // Add multicast metrics for (rlc_map_t::iterator it = rlc_array_mrb.begin(); it != rlc_array_mrb.end(); ++it) { - m.dl_tput_mbps[it->first] = (it->second->get_num_rx_bytes()*8/static_cast(1e6))/secs; + rlc_bearer_metrics_t metrics = it->second->get_metrics(); rlc_log->info("MCH_LCID=%d, RX throughput: %4.6f Mbps\n", it->first, - (it->second->get_num_rx_bytes()*8/static_cast(1e6))/secs); + (metrics.num_rx_bytes * 8 / static_cast(1e6)) / secs); + m.bearer[it->first] = metrics; } memcpy(&metrics_time[1], &metrics_time[2], sizeof(struct timeval)); @@ -382,19 +384,33 @@ void rlc::add_bearer(uint32_t lcid, rlc_config_t cnfg) rlc_common *rlc_entity = NULL; if (not valid_lcid(lcid)) { - switch (cnfg.rlc_mode) { - case rlc_mode_t::tm: - rlc_entity = new rlc_tm(rlc_log, lcid, pdcp, rrc, timers); - break; - case rlc_mode_t::am: - rlc_entity = new rlc_am_lte(rlc_log, lcid, pdcp, rrc, timers); - break; - case rlc_mode_t::um: - rlc_entity = new rlc_um(rlc_log, lcid, pdcp, rrc, timers); - break; - default: - rlc_log->error("Cannot add RLC entity - invalid mode\n"); - goto unlock_and_exit; + if (cnfg.rat == srslte_rat_t::lte) { + switch (cnfg.rlc_mode) { + case rlc_mode_t::tm: + rlc_entity = new rlc_tm(rlc_log, lcid, pdcp, rrc, timers); + break; + case rlc_mode_t::am: + rlc_entity = new rlc_am_lte(rlc_log, lcid, pdcp, rrc, timers); + break; + case rlc_mode_t::um: + rlc_entity = new rlc_um_lte(rlc_log, lcid, pdcp, rrc, timers); + break; + default: + rlc_log->error("Cannot add RLC entity - invalid mode\n"); + goto unlock_and_exit; + } + } else if (cnfg.rat == srslte_rat_t::nr) { + switch (cnfg.rlc_mode) { + case rlc_mode_t::tm: + rlc_entity = new rlc_tm(rlc_log, lcid, pdcp, rrc, timers); + break; + case rlc_mode_t::um: + rlc_entity = new rlc_um_nr(rlc_log, lcid, pdcp, rrc, timers); + break; + default: + rlc_log->error("Cannot add RLC entity - invalid mode\n"); + goto unlock_and_exit; + } } if (not rlc_array.insert(rlc_map_pair_t(lcid, rlc_entity)).second) { @@ -406,7 +422,7 @@ void rlc::add_bearer(uint32_t lcid, rlc_config_t cnfg) } // configure and add to array - if (cnfg.rlc_mode != rlc_mode_t::tm) { + if (cnfg.rlc_mode != rlc_mode_t::tm and rlc_array.find(lcid) != rlc_array.end()) { if (not rlc_array.at(lcid)->configure(cnfg)) { rlc_log->error("Error configuring RLC entity\n."); goto delete_and_exit; @@ -431,7 +447,7 @@ void rlc::add_bearer_mrb(uint32_t lcid) rlc_common* rlc_entity = NULL; if (not valid_lcid_mrb(lcid)) { - rlc_entity = new rlc_um(rlc_log, lcid, pdcp, rrc, timers); + rlc_entity = new rlc_um_lte(rlc_log, lcid, pdcp, rrc, timers); // configure and add to array if (not rlc_entity->configure(rlc_config_t::mch_config())) { rlc_log->error("Error configuring RLC entity\n."); diff --git a/lib/src/upper/rlc_am_lte.cc b/lib/src/upper/rlc_am_lte.cc index bc1b63f52..ee5d1f694 100644 --- a/lib/src/upper/rlc_am_lte.cc +++ b/lib/src/upper/rlc_am_lte.cc @@ -102,14 +102,9 @@ uint32_t rlc_am_lte::get_bearer() return lcid; } -uint32_t rlc_am_lte::get_num_rx_bytes() +rlc_bearer_metrics_t rlc_am_lte::get_metrics() { - return rx.get_num_rx_bytes(); -} - -uint32_t rlc_am_lte::get_num_tx_bytes() -{ - return tx.get_num_tx_bytes(); + return metrics; } void rlc_am_lte::reset_metrics() diff --git a/lib/src/upper/rlc_tm.cc b/lib/src/upper/rlc_tm.cc index 067ab6104..becab3444 100644 --- a/lib/src/upper/rlc_tm.cc +++ b/lib/src/upper/rlc_tm.cc @@ -126,20 +126,14 @@ uint32_t rlc_tm::get_buffer_state() return ul_queue.size_bytes(); } -uint32_t rlc_tm::get_num_tx_bytes() +rlc_bearer_metrics_t rlc_tm::get_metrics() { - return num_tx_bytes; -} - -uint32_t rlc_tm::get_num_rx_bytes() -{ - return num_rx_bytes; + return metrics; } void rlc_tm::reset_metrics() { - num_tx_bytes = 0; - num_rx_bytes = 0; + metrics = {}; } int rlc_tm::read_pdu(uint8_t *payload, uint32_t nof_bytes) @@ -163,7 +157,7 @@ int rlc_tm::read_pdu(uint8_t *payload, uint32_t nof_bytes) ul_queue.size(), ul_queue.size_bytes()); - num_tx_bytes += pdu_size; + metrics.num_tx_bytes += pdu_size; return pdu_size; } else { log->warning("Queue empty while trying to read\n"); @@ -182,7 +176,7 @@ void rlc_tm::write_pdu(uint8_t *payload, uint32_t nof_bytes) memcpy(buf->msg, payload, nof_bytes); buf->N_bytes = nof_bytes; buf->set_timestamp(); - num_rx_bytes += nof_bytes; + metrics.num_rx_bytes += nof_bytes; if (rrc->get_rb_name(lcid) == "SRB0") { rrc->write_pdu(lcid, std::move(buf)); } else { diff --git a/lib/src/upper/rlc_um.cc b/lib/src/upper/rlc_um.cc deleted file mode 100644 index 8c849c84a..000000000 --- a/lib/src/upper/rlc_um.cc +++ /dev/null @@ -1,1313 +0,0 @@ -/* - * Copyright 2013-2019 Software Radio Systems Limited - * - * This file is part of srsLTE. - * - * srsLTE is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsLTE is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#include "srslte/upper/rlc_um.h" -#include - -#define RX_MOD_BASE(x) (((x)-vr_uh - cfg.um.rx_window_size) % cfg.um.rx_mod) - -using namespace asn1::rrc; - -namespace srslte { - -rlc_um::rlc_um(srslte::log* log_, - uint32_t lcid_, - srsue::pdcp_interface_rlc* pdcp_, - srsue::rrc_interface_rlc* rrc_, - srslte::timer_handler* timers_) : - lcid(lcid_), - pool(byte_buffer_pool::get_instance()), - rrc(rrc_), - log(log_), - rx(log_, lcid_, pdcp_, rrc_, timers_) -{ -} - -// Warning: must call stop() to properly deallocate all buffers -rlc_um::~rlc_um() -{ - stop(); -} - -bool rlc_um::configure(rlc_config_t cnfg_) -{ - // determine bearer name and configure Rx/Tx objects - rb_name = get_rb_name(rrc, lcid, cnfg_.um.is_mrb); - - // store config - cfg = cnfg_; - - if (not rx.configure(cfg, rb_name)) { - return false; - } - - if (cfg.type == rlc_type_t::lte) { - tx.reset(new rlc_um_tx(log)); - if (not tx->configure(cfg, rb_name)) { - return false; - } - - log->info("%s configured in %s: t_reordering=%d ms, rx_sn_field_length=%u bits, tx_sn_field_length=%u bits\n", - rb_name.c_str(), - srslte::to_string(cnfg_.rlc_mode).c_str(), - cfg.um.t_reordering, - srslte::to_number(cfg.um.rx_sn_field_length), - srslte::to_number(cfg.um.tx_sn_field_length)); - } else { - tx.reset(new rlc_um_tx_nr(log)); - if (not tx->configure(cfg, rb_name)) { - return false; - } - - log->info("%s configured in %s: sn_field_length=%u bits\n", - rb_name.c_str(), - srslte::to_string(cnfg_.rlc_mode).c_str(), - srslte::to_number(cfg.um_nr.sn_field_length)); - } - return true; -} - -bool rlc_um::rlc_um_rx::configure(rlc_config_t cnfg_, std::string rb_name_) -{ - cfg = cnfg_; - - if (cfg.type == rlc_type_t::lte) { - if (cfg.um.rx_mod == 0) { - log->error("Error configuring %s RLC UM: rx_mod==0\n", get_rb_name()); - return false; - } - - // set reordering timer - if (reordering_timer.is_valid()) { - reordering_timer.set(cfg.um.t_reordering, [this](uint32_t tid) { timer_expired(tid); }); - } - } else { - if (cfg.um_nr.mod == 0) { - log->error("Error configuring %s RLC UM: rx_mod==0\n", get_rb_name()); - return false; - } - } - - rb_name = rb_name_; - - rx_enabled = true; - - return true; -} - -void rlc_um::empty_queue() -{ - // Drop all messages in TX SDU queue - tx->empty_queue(); -} - -bool rlc_um::is_mrb() -{ - return cfg.um.is_mrb; -} - - -void rlc_um::reestablish() -{ - tx->reestablish(); // calls stop and enables tx again - rx.reestablish(); // nothing else needed -} - - -void rlc_um::stop() -{ - if (tx) { - tx->stop(); - } - rx.stop(); -} - - -rlc_mode_t rlc_um::get_mode() -{ - return rlc_mode_t::um; -} - -uint32_t rlc_um::get_bearer() -{ - return lcid; -} - -/**************************************************************************** - * PDCP interface - ***************************************************************************/ -void rlc_um::write_sdu(unique_byte_buffer_t sdu, bool blocking) -{ - if (blocking) { - tx->write_sdu(std::move(sdu)); - } else { - tx->try_write_sdu(std::move(sdu)); - } -} - -/**************************************************************************** - * MAC interface - ***************************************************************************/ - -bool rlc_um::has_data() -{ - return tx->has_data(); -} - -uint32_t rlc_um::get_buffer_state() -{ - return tx->get_buffer_state(); -} - -int rlc_um::read_pdu(uint8_t *payload, uint32_t nof_bytes) -{ - return tx->build_data_pdu(payload, nof_bytes); -} - -void rlc_um::write_pdu(uint8_t *payload, uint32_t nof_bytes) -{ - rx.handle_data_pdu(payload, nof_bytes); -} - -uint32_t rlc_um::get_num_tx_bytes() -{ - return tx->get_num_tx_bytes(); -} - -uint32_t rlc_um::get_num_rx_bytes() -{ - return rx.get_num_rx_bytes(); -} - -void rlc_um::reset_metrics() -{ - tx->reset_metrics(); - rx.reset_metrics(); -} - - -/**************************************************************************** - * Helper functions - ***************************************************************************/ - -std::string rlc_um::get_rb_name(srsue::rrc_interface_rlc *rrc, uint32_t lcid, bool is_mrb) -{ - if (is_mrb) { - std::stringstream ss; - ss << "MRB" << lcid; - return ss.str(); - } else { - return rrc->get_rb_name(lcid); - } -} - -/**************************************************************************** - * Tx subclass implementation (base) - ***************************************************************************/ - -rlc_um::rlc_um_tx_base::rlc_um_tx_base(srslte::log* log_) : pool(byte_buffer_pool::get_instance()), log(log_) -{ -} - -rlc_um::rlc_um_tx_base::~rlc_um_tx_base() -{ -} - -void rlc_um::rlc_um_tx_base::stop() -{ - tx_enabled = false; - empty_queue(); -} - -void rlc_um::rlc_um_tx_base::reestablish() -{ - stop(); - tx_enabled = true; -} - -void rlc_um::rlc_um_tx_base::empty_queue() -{ - std::lock_guard lock(mutex); - - // deallocate all SDUs in transmit queue - while(tx_sdu_queue.size() > 0) { - unique_byte_buffer_t buf = tx_sdu_queue.read(); - } - - // deallocate SDU that is currently processed - tx_sdu.reset(); -} - -uint32_t rlc_um::rlc_um_tx_base::get_num_tx_bytes() -{ - return num_tx_bytes; -} - -void rlc_um::rlc_um_tx_base::reset_metrics() -{ - std::lock_guard lock(mutex); - num_tx_bytes = 0; -} - -bool rlc_um::rlc_um_tx_base::has_data() -{ - return (tx_sdu != NULL || !tx_sdu_queue.is_empty()); -} - -uint32_t rlc_um::rlc_um_tx_base::get_buffer_state() -{ - std::lock_guard lock(mutex); - - // Bytes needed for tx SDUs - uint32_t n_sdus = tx_sdu_queue.size(); - uint32_t n_bytes = tx_sdu_queue.size_bytes(); - if (tx_sdu) { - n_sdus++; - n_bytes += tx_sdu->N_bytes; - } - - // Room needed for header extensions? (integer rounding) - if(n_sdus > 1) { - n_bytes += ((n_sdus-1)*1.5)+0.5; - } - - // Room needed for fixed header? - if(n_bytes > 0) - n_bytes += (cfg.um.is_mrb) ? 2 : 3; - - return n_bytes; -} - -void rlc_um::rlc_um_tx_base::write_sdu(unique_byte_buffer_t sdu) -{ - if (!tx_enabled) { - return; - } - - if (sdu) { - log->info_hex(sdu->msg, sdu->N_bytes, "%s Tx SDU (%d B, tx_sdu_queue_len=%d)", get_rb_name(), sdu->N_bytes, tx_sdu_queue.size()); - tx_sdu_queue.write(std::move(sdu)); - } else { - log->warning("NULL SDU pointer in write_sdu()\n"); - } -} - -void rlc_um::rlc_um_tx_base::try_write_sdu(unique_byte_buffer_t sdu) -{ - if (!tx_enabled) { - sdu.reset(); - return; - } - - if (sdu) { - uint8_t* msg_ptr = sdu->msg; - uint32_t nof_bytes = sdu->N_bytes; - std::pair ret = tx_sdu_queue.try_write(std::move(sdu)); - if (ret.first) { - log->info_hex( - msg_ptr, nof_bytes, "%s Tx SDU (%d B, tx_sdu_queue_len=%d)", get_rb_name(), nof_bytes, tx_sdu_queue.size()); - } else { - log->info_hex(ret.second->msg, - ret.second->N_bytes, - "[Dropped SDU] %s Tx SDU (%d B, tx_sdu_queue_len=%d)", - get_rb_name(), - ret.second->N_bytes, - tx_sdu_queue.size()); - } - } else { - log->warning("NULL SDU pointer in write_sdu()\n"); - } -} - -int rlc_um::rlc_um_tx_base::build_data_pdu(uint8_t* payload, uint32_t nof_bytes) -{ - unique_byte_buffer_t pdu; - { - std::lock_guard lock(mutex); - log->debug("MAC opportunity - %d bytes\n", nof_bytes); - - if (not tx_enabled) { - return 0; - } - - if (!tx_sdu && tx_sdu_queue.size() == 0) { - log->info("No data available to be sent\n"); - return 0; - } - - pdu = allocate_unique_buffer(*pool); - if (!pdu || pdu->N_bytes != 0) { - log->error("Failed to allocate PDU buffer\n"); - return 0; - } - } - return build_data_pdu(std::move(pdu), payload, nof_bytes); -} - -const char* rlc_um::rlc_um_tx_base::get_rb_name() -{ - return rb_name.c_str(); -} - -/**************************************************************************** - * Tx Subclass implementation for LTE - ***************************************************************************/ - -rlc_um::rlc_um_tx::rlc_um_tx(srslte::log* log_) : rlc_um_tx_base(log_) {} - -bool rlc_um::rlc_um_tx::configure(rlc_config_t cnfg_, std::string rb_name_) -{ - cfg = cnfg_; - - if (cfg.um.tx_mod == 0) { - log->error("Error configuring %s RLC UM: tx_mod==0\n", get_rb_name()); - return false; - } - - tx_sdu_queue.resize(cnfg_.tx_queue_length); - - rb_name = rb_name_; - tx_enabled = true; - - return true; -} - -int rlc_um::rlc_um_tx::build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes) -{ - std::lock_guard lock(mutex); - rlc_umd_pdu_header_t header; - header.fi = RLC_FI_FIELD_START_AND_END_ALIGNED; - header.sn = vt_us; - header.N_li = 0; - header.sn_size = cfg.um.tx_sn_field_length; - - uint32_t to_move = 0; - uint32_t last_li = 0; - uint8_t *pdu_ptr = pdu->msg; - - int head_len = rlc_um_packed_length(&header); - int pdu_space = SRSLTE_MIN(nof_bytes, pdu->get_tailroom()); - - if(pdu_space <= head_len + 1) - { - log->warning("%s Cannot build a PDU - %d bytes available, %d bytes required for header\n", - get_rb_name(), nof_bytes, head_len); - return 0; - } - - // Check for SDU segment - if(tx_sdu) { - uint32_t space = pdu_space-head_len; - to_move = space >= tx_sdu->N_bytes ? tx_sdu->N_bytes : space; - log->debug("%s adding remainder of SDU segment - %d bytes of %d remaining\n", - get_rb_name(), to_move, tx_sdu->N_bytes); - memcpy(pdu_ptr, tx_sdu->msg, to_move); - last_li = to_move; - pdu_ptr += to_move; - pdu->N_bytes += to_move; - tx_sdu->N_bytes -= to_move; - tx_sdu->msg += to_move; - if(tx_sdu->N_bytes == 0) - { - log->debug("%s Complete SDU scheduled for tx. Stack latency: %ld us\n", - get_rb_name(), tx_sdu->get_latency_us()); - - tx_sdu.reset(); - } - pdu_space -= SRSLTE_MIN(to_move, pdu->get_tailroom()); - header.fi |= RLC_FI_FIELD_NOT_START_ALIGNED; // First byte does not correspond to first byte of SDU - } - - // Pull SDUs from queue - while(pdu_space > head_len + 1 && tx_sdu_queue.size() > 0) { - log->debug("pdu_space=%d, head_len=%d\n", pdu_space, head_len); - if(last_li > 0) - header.li[header.N_li++] = last_li; - head_len = rlc_um_packed_length(&header); - tx_sdu = tx_sdu_queue.read(); - uint32_t space = pdu_space-head_len; - to_move = space >= tx_sdu->N_bytes ? tx_sdu->N_bytes : space; - log->debug("%s adding new SDU segment - %d bytes of %d remaining\n", - get_rb_name(), to_move, tx_sdu->N_bytes); - memcpy(pdu_ptr, tx_sdu->msg, to_move); - last_li = to_move; - pdu_ptr += to_move; - pdu->N_bytes += to_move; - tx_sdu->N_bytes -= to_move; - tx_sdu->msg += to_move; - if(tx_sdu->N_bytes == 0) { - log->debug("%s Complete SDU scheduled for tx. Stack latency: %ld us\n", - get_rb_name(), tx_sdu->get_latency_us()); - - tx_sdu.reset(); - } - pdu_space -= to_move; - } - - if(tx_sdu) { - header.fi |= RLC_FI_FIELD_NOT_END_ALIGNED; // Last byte does not correspond to last byte of SDU - } - - // Set SN - header.sn = vt_us; - vt_us = (vt_us + 1) % cfg.um.tx_mod; - - // Add header and TX - rlc_um_write_data_pdu_header(&header, pdu.get()); - memcpy(payload, pdu->msg, pdu->N_bytes); - uint32_t ret = pdu->N_bytes; - - log->info_hex(payload, ret, "%s Tx PDU SN=%d (%d B)\n", get_rb_name(), header.sn, pdu->N_bytes); - - debug_state(); - - num_tx_bytes += ret; - - return ret; -} - -void rlc_um::rlc_um_tx::debug_state() -{ - log->debug("%s vt_us = %d\n", get_rb_name(), vt_us); -} - -/**************************************************************************** - * Tx Subclass implementation for NR - ***************************************************************************/ - -rlc_um::rlc_um_tx_nr::rlc_um_tx_nr(srslte::log* log_) : rlc_um_tx_base(log_) {} - -bool rlc_um::rlc_um_tx_nr::configure(rlc_config_t cnfg_, std::string rb_name_) -{ - cfg = cnfg_; - - if (cfg.um_nr.mod == 0) { - log->error("Error configuring %s RLC UM: tx_mod==0\n", get_rb_name()); - return false; - } - - tx_sdu_queue.resize(cnfg_.tx_queue_length); - - rb_name = rb_name_; - tx_enabled = true; - - return true; -} - -int rlc_um::rlc_um_tx_nr::build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes) -{ - std::lock_guard lock(mutex); - rlc_um_nr_pdu_header_t header = {}; - header.si = rlc_nr_si_field_t::full_sdu; - header.sn = TX_Next; - header.sn_size = cfg.um_nr.sn_field_length; - - uint32_t to_move = 0; - uint8_t* pdu_ptr = pdu->msg; - - int head_len = rlc_um_nr_packed_length(header); - int pdu_space = SRSLTE_MIN(nof_bytes, pdu->get_tailroom()); - - if (pdu_space <= head_len + 1) { - log->warning("%s Cannot build a PDU - %d bytes available, %d bytes required for header\n", - get_rb_name(), - nof_bytes, - head_len); - return 0; - } - - // Check for SDU segment - if (tx_sdu) { - uint32_t space = pdu_space - head_len; - to_move = space >= tx_sdu->N_bytes ? tx_sdu->N_bytes : space; - log->debug( - "%s adding remainder of SDU segment - %d bytes of %d remaining\n", get_rb_name(), to_move, tx_sdu->N_bytes); - memcpy(pdu_ptr, tx_sdu->msg, to_move); - pdu_ptr += to_move; - pdu->N_bytes += to_move; - tx_sdu->N_bytes -= to_move; - tx_sdu->msg += to_move; - if (tx_sdu->N_bytes == 0) { - log->debug("%s Complete SDU scheduled for tx. Stack latency: %ld us\n", get_rb_name(), tx_sdu->get_latency_us()); - tx_sdu.reset(); - header.si = rlc_nr_si_field_t::last_segment; - } else { - header.si = rlc_nr_si_field_t::neither_first_nor_last_segment; - } - pdu_space -= SRSLTE_MIN(to_move, pdu->get_tailroom()); - } else { - // Pull SDU from queue - log->debug("pdu_space=%d, head_len=%d\n", pdu_space, head_len); - - head_len = rlc_um_nr_packed_length(header); - tx_sdu = tx_sdu_queue.read(); - uint32_t space = pdu_space - head_len; - to_move = space >= tx_sdu->N_bytes ? tx_sdu->N_bytes : space; - log->debug("%s adding new SDU - %d bytes of %d remaining\n", get_rb_name(), to_move, tx_sdu->N_bytes); - memcpy(pdu_ptr, tx_sdu->msg, to_move); - pdu_ptr += to_move; - pdu->N_bytes += to_move; - tx_sdu->N_bytes -= to_move; - tx_sdu->msg += to_move; - if (tx_sdu->N_bytes == 0) { - log->debug("%s Complete SDU scheduled for tx. Stack latency: %ld us\n", get_rb_name(), tx_sdu->get_latency_us()); - tx_sdu.reset(); - header.si = rlc_nr_si_field_t::full_sdu; - } else { - header.si = rlc_nr_si_field_t::first_segment; - } - pdu_space -= to_move; - } - - // Update SN if needed - if (header.si == rlc_nr_si_field_t::last_segment) { - TX_Next = (TX_Next + 1) % cfg.um_nr.mod; - } - - // Add header and TX - rlc_um_nr_write_data_pdu_header(header, pdu.get()); - memcpy(payload, pdu->msg, pdu->N_bytes); - uint32_t ret = pdu->N_bytes; - - log->info_hex(payload, ret, "%s Tx PDU SN=%d (%d B)\n", get_rb_name(), header.sn, pdu->N_bytes); - - debug_state(); - - num_tx_bytes += ret; - - return ret; -} - -void rlc_um::rlc_um_tx_nr::debug_state() -{ - log->debug("%s TX_Next = %d\n", get_rb_name(), TX_Next); -} - -/**************************************************************************** - * Rx subclass implementation - ***************************************************************************/ - -rlc_um::rlc_um_rx::rlc_um_rx(srslte::log* log_, - uint32_t lcid_, - srsue::pdcp_interface_rlc* pdcp_, - srsue::rrc_interface_rlc* rrc_, - srslte::timer_handler* timers_) : - pool(byte_buffer_pool::get_instance()), - log(log_), - pdcp(pdcp_), - rrc(rrc_), - timers(timers_), - lcid(lcid_), - reordering_timer(timers_->get_unique_timer()) -{ -} - -rlc_um::rlc_um_rx::~rlc_um_rx() {} - -void rlc_um::rlc_um_rx::reestablish() -{ - // try to reassemble any SDUs if possible - if (reordering_timer.is_valid()) { - if (reordering_timer.is_running()) { - reordering_timer.stop(); - timer_expired(reordering_timer.id()); - } - } - - std::lock_guard lock(mutex); - reset(); - rx_enabled = true; -} - -void rlc_um::rlc_um_rx::stop() -{ - std::lock_guard lock(mutex); - reset(); - - reordering_timer.stop(); -} - -void rlc_um::rlc_um_rx::reset() -{ - vr_ur = 0; - vr_ux = 0; - vr_uh = 0; - pdu_lost = false; - rx_enabled = false; - - rx_sdu.reset(); - - // Drop all messages in RX window - rx_window.clear(); -} - - -void rlc_um::rlc_um_rx::handle_data_pdu(uint8_t *payload, uint32_t nof_bytes) -{ - std::lock_guard lock(mutex); - - rlc_umd_pdu_t pdu; - int header_len = 0; - std::map::iterator it; - rlc_umd_pdu_header_t header; - - if (!rx_enabled) { - return; - } - - num_rx_bytes += nof_bytes; - - rlc_um_read_data_pdu_header(payload, nof_bytes, cfg.um.rx_sn_field_length, &header); - - log->info_hex(payload, nof_bytes, "RX %s Rx data PDU SN: %d (%d B)", get_rb_name(), header.sn, nof_bytes); - - if (RX_MOD_BASE(header.sn) >= RX_MOD_BASE(vr_uh - cfg.um.rx_window_size) && - RX_MOD_BASE(header.sn) < RX_MOD_BASE(vr_ur)) { - log->info("%s SN: %d outside rx window [%d:%d] - discarding\n", - get_rb_name(), header.sn, vr_ur, vr_uh); - return; - } - it = rx_window.find(header.sn); - if(rx_window.end() != it) - { - log->info("%s Discarding duplicate SN: %d\n", get_rb_name(), header.sn); - return; - } - - // Write to rx window - pdu.buf = allocate_unique_buffer(*pool); - if (!pdu.buf) { - log->error("Discarting packet: no space in buffer pool\n"); - return; - } - memcpy(pdu.buf->msg, payload, nof_bytes); - pdu.buf->N_bytes = nof_bytes; - //Strip header from PDU - header_len = rlc_um_packed_length(&header); - pdu.buf->msg += header_len; - pdu.buf->N_bytes -= header_len; - pdu.header = header; - rx_window[header.sn] = std::move(pdu); - - // Update vr_uh - if(!inside_reordering_window(header.sn)) { - vr_uh = (header.sn + 1) % cfg.um.rx_mod; - } - - // Reassemble and deliver SDUs, while updating vr_ur - log->debug("Entering Reassemble from received PDU\n"); - reassemble_rx_sdus(); - log->debug("Finished reassemble from received PDU\n"); - - // Update reordering variables and timers - if (reordering_timer.is_running()) { - if (RX_MOD_BASE(vr_ux) <= RX_MOD_BASE(vr_ur) || (!inside_reordering_window(vr_ux) && vr_ux != vr_uh)) { - reordering_timer.stop(); - } - } - if (!reordering_timer.is_running()) { - if (RX_MOD_BASE(vr_uh) > RX_MOD_BASE(vr_ur)) { - reordering_timer.run(); - vr_ux = vr_uh; - } - } - - debug_state(); -} - - -// No locking required as only called from within handle_data_pdu and timer_expired which lock -void rlc_um::rlc_um_rx::reassemble_rx_sdus() -{ - if(!rx_sdu) { - rx_sdu = allocate_unique_buffer(*pool); - if (!rx_sdu) { - log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); - return; - } - } - - // First catch up with lower edge of reordering window - while(!inside_reordering_window(vr_ur)) - { - log->debug("SN=%d is not inside reordering windows\n", vr_ur); - - if(rx_window.end() == rx_window.find(vr_ur)) - { - log->debug("SN=%d not in rx_window. Reset received SDU\n", vr_ur); - rx_sdu->clear(); - }else{ - // Handle any SDU segments - for(uint32_t i=0; idebug_hex(rx_window[vr_ur].buf->msg, len, "Handling segment %d/%d of length %d B of SN=%d\n", i+1, rx_window[vr_ur].header.N_li, len, vr_ur); - // Check if we received a middle or end segment - if (rx_sdu->N_bytes == 0 && i == 0 && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { - log->warning("Dropping PDU %d due to lost start segment\n", vr_ur); - // Advance data pointers and continue with next segment - rx_window[vr_ur].buf->msg += len; - rx_window[vr_ur].buf->N_bytes -= len; - rx_sdu->clear(); - break; - } - - memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, len); - rx_sdu->N_bytes += len; - rx_window[vr_ur].buf->msg += len; - rx_window[vr_ur].buf->N_bytes -= len; - if ((pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) || - (vr_ur != ((vr_ur_in_rx_sdu + 1) % cfg.um.rx_mod))) { - log->warning("Dropping remainder of lost PDU (lower edge middle segments, vr_ur=%d, vr_ur_in_rx_sdu=%d)\n", vr_ur, vr_ur_in_rx_sdu); - rx_sdu->clear(); - } else { - log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d, i=%d (lower edge middle segments)", get_rb_name(), vr_ur, i); - rx_sdu->set_timestamp(); - if (cfg.um.is_mrb) { - pdcp->write_pdu_mch(lcid, std::move(rx_sdu)); - } else { - pdcp->write_pdu(lcid, std::move(rx_sdu)); - } - rx_sdu = allocate_unique_buffer(*pool); - if (!rx_sdu) { - log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); - return; - } - } - pdu_lost = false; - } - - // Handle last segment - if (rx_sdu->N_bytes > 0 || rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { - log->info("Writing last segment in SDU buffer. Lower edge vr_ur=%d, Buffer size=%d, segment size=%d\n", - vr_ur, rx_sdu->N_bytes, rx_window[vr_ur].buf->N_bytes); - - memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, rx_window[vr_ur].buf->N_bytes); - rx_sdu->N_bytes += rx_window[vr_ur].buf->N_bytes; - vr_ur_in_rx_sdu = vr_ur; - if(rlc_um_end_aligned(rx_window[vr_ur].header.fi)) - { - if(pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { - log->warning("Dropping remainder of lost PDU (lower edge last segments)\n"); - rx_sdu->clear(); - } else { - log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (lower edge last segments)", get_rb_name(), vr_ur); - rx_sdu->set_timestamp(); - if (cfg.um.is_mrb) { - pdcp->write_pdu_mch(lcid, std::move(rx_sdu)); - } else { - pdcp->write_pdu(lcid, std::move(rx_sdu)); - } - rx_sdu = allocate_unique_buffer(*pool); - if (!rx_sdu) { - log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); - return; - } - } - pdu_lost = false; - } - } - - // Clean up rx_window - rx_window.erase(vr_ur); - } - - vr_ur = (vr_ur + 1) % cfg.um.rx_mod; - } - - // Now update vr_ur until we reach an SN we haven't yet received - while(rx_window.end() != rx_window.find(vr_ur)) { - log->debug("Reassemble loop for vr_ur=%d\n", vr_ur); - - if (not pdu_belongs_to_rx_sdu()) { - log->warning("PDU SN=%d lost, stop reassambling SDU (vr_ur_in_rx_sdu=%d)\n", vr_ur_in_rx_sdu+1, vr_ur_in_rx_sdu); - pdu_lost = false; // Reset flag to not prevent reassembling of further segments - rx_sdu->clear(); - } - - // Handle any SDU segments - for(uint32_t i=0; idebug("Handling SDU segment i=%d with len=%d of vr_ur=%d N_li=%d [%s]\n", i, len, vr_ur, rx_window[vr_ur].header.N_li, rlc_fi_field_text[rx_window[vr_ur].header.fi]); - // Check if the first part of the PDU is a middle or end segment - if (rx_sdu->N_bytes == 0 && i == 0 && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { - log->warning_hex(rx_window[vr_ur].buf->msg, len, "Dropping first %d B of SN %d due to lost start segment\n", len, vr_ur); - - if (rx_window[vr_ur].buf->N_bytes < len) { - log->error("Dropping remaining remainder of SN %d too (N_bytes=%u < len=%d)\n", vr_ur, rx_window[vr_ur].buf->N_bytes, len); - goto clean_up_rx_window; - } - - // Advance data pointers and continue with next segment - rx_window[vr_ur].buf->msg += len; - rx_window[vr_ur].buf->N_bytes -= len; - rx_sdu->clear(); - - // Reset flag, it is safe to process all remaining segments of this PDU - pdu_lost = false; - continue; - } - - // Check available space in SDU - if ((uint32_t)len > rx_sdu->get_tailroom()) { - log->error("Dropping PDU %d due to buffer mis-alignment (current segment len %d B, received %d B)\n", vr_ur, rx_sdu->N_bytes, len); - rx_sdu->clear(); - goto clean_up_rx_window; - } - - if (not pdu_belongs_to_rx_sdu()) { - log->info_hex(rx_window[vr_ur].buf->msg, len, "Copying first %d bytes of new SDU\n", len); - log->info("Updating vr_ur_in_rx_sdu. old=%d, new=%d\n", vr_ur_in_rx_sdu, vr_ur); - vr_ur_in_rx_sdu = vr_ur; - } else { - log->info_hex(rx_window[vr_ur].buf->msg, - len, - "Concatenating %d bytes in to current length %d. rx_window remaining bytes=%d, " - "vr_ur_in_rx_sdu=%d, vr_ur=%d, rx_mod=%d, last_mod=%d\n", - len, - rx_sdu->N_bytes, - rx_window[vr_ur].buf->N_bytes, - vr_ur_in_rx_sdu, - vr_ur, - cfg.um.rx_mod, - (vr_ur_in_rx_sdu + 1) % cfg.um.rx_mod); - } - - memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, len); - rx_sdu->N_bytes += len; - rx_window[vr_ur].buf->msg += len; - rx_window[vr_ur].buf->N_bytes -= len; - vr_ur_in_rx_sdu = vr_ur; - - if (pdu_belongs_to_rx_sdu()) { - log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d, i=%d, (update vr_ur middle segments)", get_rb_name(), vr_ur, i); - rx_sdu->set_timestamp(); - if (cfg.um.is_mrb) { - pdcp->write_pdu_mch(lcid, std::move(rx_sdu)); - } else { - pdcp->write_pdu(lcid, std::move(rx_sdu)); - } - rx_sdu = allocate_unique_buffer(*pool); - if (!rx_sdu) { - log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); - return; - } - } else { - log->warning("Dropping remainder of lost PDU (update vr_ur middle segments, vr_ur=%d, vr_ur_in_rx_sdu=%d)\n", vr_ur, vr_ur_in_rx_sdu); - // Advance data pointers and continue with next segment - rx_window[vr_ur].buf->msg += len; - rx_window[vr_ur].buf->N_bytes -= len; - } - pdu_lost = false; - } - - // Handle last segment - if (rx_sdu->N_bytes == 0 && rx_window[vr_ur].header.N_li == 0 && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { - log->warning("Dropping PDU %d due to lost start segment\n", vr_ur); - rx_sdu->clear(); - goto clean_up_rx_window; - } - - if (rx_sdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES && - rx_window[vr_ur].buf->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES && - rx_window[vr_ur].buf->N_bytes + rx_sdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES) - { - log->info_hex(rx_window[vr_ur].buf->msg, rx_window[vr_ur].buf->N_bytes, "Writing last segment in SDU buffer. Updating vr_ur=%d, vr_ur_in_rx_sdu=%d, Buffer size=%d, segment size=%d\n", - vr_ur, vr_ur_in_rx_sdu, rx_sdu->N_bytes, rx_window[vr_ur].buf->N_bytes); - memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, rx_window[vr_ur].buf->N_bytes); - rx_sdu->N_bytes += rx_window[vr_ur].buf->N_bytes; - } else { - log->error("Out of bounds while reassembling SDU buffer in UM: sdu_len=%d, window_buffer_len=%d, vr_ur=%d\n", - rx_sdu->N_bytes, rx_window[vr_ur].buf->N_bytes, vr_ur); - } - vr_ur_in_rx_sdu = vr_ur; - if(rlc_um_end_aligned(rx_window[vr_ur].header.fi)) { - if(pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { - log->warning("Dropping remainder of lost PDU (update vr_ur last segments)\n"); - rx_sdu->clear(); - } else { - log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (update vr_ur last segments)", get_rb_name(), vr_ur); - rx_sdu->set_timestamp(); - if (cfg.um.is_mrb) { - pdcp->write_pdu_mch(lcid, std::move(rx_sdu)); - } else { - pdcp->write_pdu(lcid, std::move(rx_sdu)); - } - rx_sdu = allocate_unique_buffer(*pool); - if (!rx_sdu) { - log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); - return; - } - } - pdu_lost = false; - } - -clean_up_rx_window: - // Clean up rx_window - rx_window.erase(vr_ur); - - vr_ur = (vr_ur + 1) % cfg.um.rx_mod; - } -} - - -// Only called when lock is hold -bool rlc_um::rlc_um_rx::pdu_belongs_to_rx_sdu() -{ - // return true if the currently received SDU - if (((vr_ur_in_rx_sdu + 1) % cfg.um.rx_mod == vr_ur) || (vr_ur == vr_ur_in_rx_sdu)) { - return true; - } - return false; -} - - -// Only called when lock is hold -// 36.322 Section 5.1.2.2.1 -bool rlc_um::rlc_um_rx::inside_reordering_window(uint16_t sn) -{ - if (cfg.um.rx_window_size == 0 || rx_window.empty()) { - return true; - } - if (RX_MOD_BASE(vr_uh - cfg.um.rx_window_size) <= RX_MOD_BASE(sn) && RX_MOD_BASE(sn) < RX_MOD_BASE(vr_uh)) { - return true; - } else { - return false; - } -} - - -uint32_t rlc_um::rlc_um_rx::get_num_rx_bytes() -{ - return num_rx_bytes; -} - - -void rlc_um::rlc_um_rx::reset_metrics() -{ - std::lock_guard lock(mutex); - num_rx_bytes = 0; -} - - -/**************************************************************************** - * Timeout callback interface - ***************************************************************************/ - -void rlc_um::rlc_um_rx::timer_expired(uint32_t timeout_id) -{ - std::lock_guard lock(mutex); - if (reordering_timer.is_valid() && reordering_timer.id() == timeout_id) { - // 36.322 v10 Section 5.1.2.2.4 - log->info("%s reordering timeout expiry - updating vr_ur and reassembling\n", get_rb_name()); - - log->warning("Lost PDU SN: %d\n", vr_ur); - - pdu_lost = true; - if (rx_sdu != NULL) { - rx_sdu->clear(); - } - - while(RX_MOD_BASE(vr_ur) < RX_MOD_BASE(vr_ux)) { - vr_ur = (vr_ur + 1) % cfg.um.rx_mod; - log->debug("Entering Reassemble from timeout id=%d\n", timeout_id); - reassemble_rx_sdus(); - log->debug("Finished reassemble from timeout id=%d\n", timeout_id); - } - - if (RX_MOD_BASE(vr_uh) > RX_MOD_BASE(vr_ur)) { - reordering_timer.run(); - vr_ux = vr_uh; - } - - debug_state(); - } -} - -/**************************************************************************** - * Helper functions - ***************************************************************************/ - -void rlc_um::rlc_um_rx::debug_state() -{ - log->debug("%s vr_ur = %d, vr_ux = %d, vr_uh = %d\n", get_rb_name(), vr_ur, vr_ux, vr_uh); -} - -const char* rlc_um::rlc_um_rx::get_rb_name() -{ - return rb_name.c_str(); -} - - -/**************************************************************************** - * Header pack/unpack helper functions - * Ref: 3GPP TS 36.322 v10.0.0 Section 6.2.1 - ***************************************************************************/ - -void rlc_um_read_data_pdu_header(byte_buffer_t* pdu, rlc_umd_sn_size_t sn_size, rlc_umd_pdu_header_t* header) -{ - rlc_um_read_data_pdu_header(pdu->msg, pdu->N_bytes, sn_size, header); -} - -void rlc_um_read_data_pdu_header(uint8_t *payload, uint32_t nof_bytes, rlc_umd_sn_size_t sn_size, rlc_umd_pdu_header_t *header) -{ - uint8_t ext; - uint8_t *ptr = payload; - // Fixed part - if (sn_size == rlc_umd_sn_size_t::size5bits) { - header->fi = (rlc_fi_field_t)((*ptr >> 6) & 0x03); // 2 bits FI - ext = ((*ptr >> 5) & 0x01); // 1 bit EXT - header->sn = *ptr & 0x1F; // 5 bits SN - ptr++; - }else{ - header->fi = (rlc_fi_field_t)((*ptr >> 3) & 0x03); // 2 bits FI - ext = ((*ptr >> 2) & 0x01); // 1 bit EXT - header->sn = (*ptr & 0x03) << 8; // 2 bits SN - ptr++; - header->sn |= (*ptr & 0xFF); // 8 bits SN - ptr++; - } - - header->sn_size = sn_size; - - // Extension part - header->N_li = 0; - while(ext) - { - if(header->N_li%2 == 0) - { - ext = ((*ptr >> 7) & 0x01); - header->li[header->N_li] = (*ptr & 0x7F) << 4; // 7 bits of LI - ptr++; - header->li[header->N_li] |= (*ptr & 0xF0) >> 4; // 4 bits of LI - header->N_li++; - } - else - { - ext = (*ptr >> 3) & 0x01; - header->li[header->N_li] = (*ptr & 0x07) << 8; // 3 bits of LI - ptr++; - header->li[header->N_li] |= (*ptr & 0xFF); // 8 bits of LI - header->N_li++; - ptr++; - } - } -} - -void rlc_um_write_data_pdu_header(rlc_umd_pdu_header_t* header, byte_buffer_t* pdu) -{ - uint32_t i; - uint8_t ext = (header->N_li > 0) ? 1 : 0; - // Make room for the header - uint32_t len = rlc_um_packed_length(header); - pdu->msg -= len; - uint8_t *ptr = pdu->msg; - - // Fixed part - if (header->sn_size == rlc_umd_sn_size_t::size5bits) { - *ptr = (header->fi & 0x03) << 6; // 2 bits FI - *ptr |= (ext & 0x01) << 5; // 1 bit EXT - *ptr |= header->sn & 0x1F; // 5 bits SN - ptr++; - }else{ - *ptr = (header->fi & 0x03) << 3; // 3 Reserved bits | 2 bits FI - *ptr |= (ext & 0x01) << 2; // 1 bit EXT - *ptr |= (header->sn & 0x300) >> 8; // 2 bits SN - ptr++; - *ptr = (header->sn & 0xFF); // 8 bits SN - ptr++; - } - - // Extension part - i = 0; - while(i < header->N_li) - { - ext = ((i+1) == header->N_li) ? 0 : 1; - *ptr = (ext & 0x01) << 7; // 1 bit header - *ptr |= (header->li[i] & 0x7F0) >> 4; // 7 bits of LI - ptr++; - *ptr = (header->li[i] & 0x00F) << 4; // 4 bits of LI - i++; - if(i < header->N_li) - { - ext = ((i+1) == header->N_li) ? 0 : 1; - *ptr |= (ext & 0x01) << 3; // 1 bit header - *ptr |= (header->li[i] & 0x700) >> 8; // 3 bits of LI - ptr++; - *ptr = (header->li[i] & 0x0FF); // 8 bits of LI - ptr++; - i++; - } - } - // Pad if N_li is odd - if(header->N_li%2 == 1) - ptr++; - - pdu->N_bytes += ptr-pdu->msg; -} - -uint32_t rlc_um_packed_length(rlc_umd_pdu_header_t *header) -{ - uint32_t len = 0; - if (header->sn_size == rlc_umd_sn_size_t::size5bits) { - len += 1; // Fixed part is 1 byte - }else{ - len += 2; // Fixed part is 2 bytes - } - len += header->N_li * 1.5 + 0.5; // Extension part - integer rounding up - return len; -} - -bool rlc_um_start_aligned(uint8_t fi) -{ - return (fi == RLC_FI_FIELD_START_AND_END_ALIGNED || fi == RLC_FI_FIELD_NOT_END_ALIGNED); -} - -bool rlc_um_end_aligned(uint8_t fi) -{ - return (fi == RLC_FI_FIELD_START_AND_END_ALIGNED || fi == RLC_FI_FIELD_NOT_START_ALIGNED); -} - -/**************************************************************************** - * Header pack/unpack helper functions - * Ref: 3GPP TS 38.322 v15.3.0 Section 6.2.2.3 - ***************************************************************************/ - -uint32_t rlc_um_nr_read_data_pdu_header(const byte_buffer_t* pdu, - const rlc_um_nr_sn_size_t sn_size, - rlc_um_nr_pdu_header_t* header) -{ - return rlc_um_nr_read_data_pdu_header(pdu->msg, pdu->N_bytes, sn_size, header); -} - -uint32_t rlc_um_nr_read_data_pdu_header(const uint8_t* payload, - const uint32_t nof_bytes, - const rlc_um_nr_sn_size_t sn_size, - rlc_um_nr_pdu_header_t* header) -{ - uint8_t* ptr = const_cast(payload); - - header->sn_size = sn_size; - - // Fixed part - if (sn_size == rlc_um_nr_sn_size_t::size6bits) { - header->si = (rlc_nr_si_field_t)((*ptr >> 6) & 0x03); // 2 bits SI - header->sn = *ptr & 0x3F; // 6 bits SN - // sanity check - if (header->si == rlc_nr_si_field_t::full_sdu and not header->sn == 0) { - fprintf(stderr, "Malformed PDU, reserved bits are set.\n"); - return 0; - } - ptr++; - } else if (sn_size == rlc_um_nr_sn_size_t::size12bits) { - header->si = (rlc_nr_si_field_t)((*ptr >> 6) & 0x03); // 2 bits SI - header->sn = (*ptr & 0x0F) << 4; // 4 bits SN - - // sanity check - if (header->si == rlc_nr_si_field_t::first_segment) { - // make sure two reserved bits are not set - if (((*ptr >> 4) & 0x03) != 0) { - fprintf(stderr, "Malformed PDU, reserved bits are set.\n"); - return 0; - } - } - - // continue unpacking remaining SN - ptr++; - header->sn |= (*ptr & 0xFF); // 8 bits SN - ptr++; - } else { - fprintf(stderr, "Unsupported SN length\n"); - return 0; - } - - // Read optional part - if (header->si == rlc_nr_si_field_t::last_segment || - header->si == rlc_nr_si_field_t::neither_first_nor_last_segment) { - // read SO - header->so = (*ptr & 0xFF) << 8; - ptr++; - header->so |= (*ptr & 0xFF); - ptr++; - } - - // return consumed bytes - return (ptr - payload); -} - -uint32_t rlc_um_nr_packed_length(const rlc_um_nr_pdu_header_t& header) -{ - uint32_t len = 0; - if (header.si == rlc_nr_si_field_t::full_sdu || header.si == rlc_nr_si_field_t::first_segment) { - len = 1; - if (header.sn_size == rlc_um_nr_sn_size_t::size12bits) { - len++; - } - } else { - if (header.sn_size == rlc_um_nr_sn_size_t::size6bits) { - len = 3; - } else { - len = 4; - } - } - return len; -} - -uint32_t rlc_um_nr_write_data_pdu_header(const rlc_um_nr_pdu_header_t& header, byte_buffer_t* pdu) -{ - // Make room for the header - uint32_t len = rlc_um_nr_packed_length(header); - pdu->msg -= len; - uint8_t* ptr = pdu->msg; - - // write SI field - *ptr = (header.si & 0x03) << 6; // 2 bits SI - - if (header.si == rlc_nr_si_field_t::full_sdu) { - // that's all .. - ptr++; - } else { - if (header.sn_size == rlc_um_nr_sn_size_t::size6bits) { - // write SN - *ptr |= (header.sn & 0x3f); // 6 bit SN - ptr++; - } else { - // 12bit SN - *ptr |= (header.sn & 0xf); // 4 bit SN - ptr++; - *ptr = (header.sn & 0xFF); // remaining 8 bit SN - ptr++; - } - if (header.so) { - // write SO - *ptr = (header.so) >> 8; // first part of SO - ptr++; - *ptr = (header.so & 0xFF); // second part of SO - ptr++; - } - } - - pdu->N_bytes += ptr - pdu->msg; - - return len; -} - -} // namespace srslte diff --git a/lib/src/upper/rlc_um_base.cc b/lib/src/upper/rlc_um_base.cc new file mode 100644 index 000000000..517835084 --- /dev/null +++ b/lib/src/upper/rlc_um_base.cc @@ -0,0 +1,285 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "srslte/upper/rlc_um_base.h" +#include + +using namespace asn1::rrc; + +namespace srslte { + +rlc_um_base::rlc_um_base(srslte::log* log_, + uint32_t lcid_, + srsue::pdcp_interface_rlc* pdcp_, + srsue::rrc_interface_rlc* rrc_, + srslte::timer_handler* timers_) : + log(log_), lcid(lcid_), pdcp(pdcp_), rrc(rrc_), timers(timers_), pool(byte_buffer_pool::get_instance()) +{ +} + +rlc_um_base::~rlc_um_base() {} + +void rlc_um_base::stop() +{ + if (tx) { + tx->stop(); + } + + if (rx) { + rx->stop(); + } +} + +rlc_mode_t rlc_um_base::get_mode() +{ + return rlc_mode_t::um; +} + +uint32_t rlc_um_base::get_bearer() +{ + return lcid; +} + +bool rlc_um_base::is_mrb() +{ + return cfg.um.is_mrb; +} + +void rlc_um_base::reestablish() +{ + tx_enabled = false; + + if (tx) { + tx->reestablish(); // calls stop and enables tx again + } + + if (rx) { + rx->reestablish(); // nothing else needed + } + + tx_enabled = true; +} + +void rlc_um_base::empty_queue() +{ + // Drop all messages in TX SDU queue + if (tx) { + tx->empty_queue(); + } +} + +/**************************************************************************** + * PDCP interface + ***************************************************************************/ +void rlc_um_base::write_sdu(unique_byte_buffer_t sdu, bool blocking) +{ + if (not tx_enabled || not tx) { + log->debug("%s is currently deactivated. Dropping SDU (%d B)\n", rb_name.c_str(), sdu->N_bytes); + metrics.num_dropped_sdus++; + return; + } + + if (blocking) { + tx->write_sdu(std::move(sdu)); + } else { + tx->try_write_sdu(std::move(sdu)); + } +} + +/**************************************************************************** + * MAC interface + ***************************************************************************/ + +bool rlc_um_base::has_data() +{ + if (tx) { + return tx->has_data(); + } + return false; +} + +uint32_t rlc_um_base::get_buffer_state() +{ + if (tx) { + return tx->get_buffer_state(); + } + return 0; +} + +int rlc_um_base::read_pdu(uint8_t* payload, uint32_t nof_bytes) +{ + if (tx && tx_enabled) { + uint32_t len = tx->build_data_pdu(payload, nof_bytes); + metrics.num_tx_bytes += len; + metrics.num_tx_pdus++; + return len; + } + return 0; +} + +void rlc_um_base::write_pdu(uint8_t* payload, uint32_t nof_bytes) +{ + if (rx && rx_enabled) { + metrics.num_rx_pdus++; + metrics.num_rx_bytes += nof_bytes; + rx->handle_data_pdu(payload, nof_bytes); + } +} + +rlc_bearer_metrics_t rlc_um_base::get_metrics() +{ + return metrics; +} + +void rlc_um_base::reset_metrics() +{ + metrics = {}; +} + +/**************************************************************************** + * Helper functions + ***************************************************************************/ + +std::string rlc_um_base::get_rb_name(srsue::rrc_interface_rlc* rrc, uint32_t lcid, bool is_mrb) +{ + if (is_mrb) { + std::stringstream ss; + ss << "MRB" << lcid; + return ss.str(); + } else { + return rrc->get_rb_name(lcid); + } +} + +/**************************************************************************** + * Rx subclass implementation (base) + ***************************************************************************/ + +rlc_um_base::rlc_um_base_rx::rlc_um_base_rx(rlc_um_base* parent_) : + pool(parent_->pool), + log(parent_->log), + timers(parent_->timers), + pdcp(parent_->pdcp), + rrc(parent_->rrc), + cfg(parent_->cfg), + metrics(parent_->metrics), + lcid(parent_->lcid) +{ +} + +rlc_um_base::rlc_um_base_rx::~rlc_um_base_rx() {} + +/**************************************************************************** + * Tx subclass implementation (base) + ***************************************************************************/ + +rlc_um_base::rlc_um_base_tx::rlc_um_base_tx(rlc_um_base* parent_) : log(parent_->log), pool(parent_->pool) {} + +rlc_um_base::rlc_um_base_tx::~rlc_um_base_tx() {} + +void rlc_um_base::rlc_um_base_tx::stop() +{ + empty_queue(); +} + +void rlc_um_base::rlc_um_base_tx::reestablish() +{ + stop(); + // bearer is enabled in base class +} + +void rlc_um_base::rlc_um_base_tx::empty_queue() +{ + std::lock_guard lock(mutex); + + // deallocate all SDUs in transmit queue + while (not tx_sdu_queue.is_empty()) { + unique_byte_buffer_t buf = tx_sdu_queue.read(); + } + + // deallocate SDU that is currently processed + tx_sdu.reset(); +} + +bool rlc_um_base::rlc_um_base_tx::has_data() +{ + return (tx_sdu != nullptr || !tx_sdu_queue.is_empty()); +} + +void rlc_um_base::rlc_um_base_tx::write_sdu(unique_byte_buffer_t sdu) +{ + if (sdu) { + log->info_hex(sdu->msg, + sdu->N_bytes, + "%s Tx SDU (%d B, tx_sdu_queue_len=%d)", + rb_name.c_str(), + sdu->N_bytes, + tx_sdu_queue.size()); + tx_sdu_queue.write(std::move(sdu)); + } else { + log->warning("NULL SDU pointer in write_sdu()\n"); + } +} + +void rlc_um_base::rlc_um_base_tx::try_write_sdu(unique_byte_buffer_t sdu) +{ + if (sdu) { + uint8_t* msg_ptr = sdu->msg; + uint32_t nof_bytes = sdu->N_bytes; + std::pair ret = tx_sdu_queue.try_write(std::move(sdu)); + if (ret.first) { + log->info_hex( + msg_ptr, nof_bytes, "%s Tx SDU (%d B, tx_sdu_queue_len=%d)", rb_name.c_str(), nof_bytes, tx_sdu_queue.size()); + } else { + log->info_hex(ret.second->msg, + ret.second->N_bytes, + "[Dropped SDU] %s Tx SDU (%d B, tx_sdu_queue_len=%d)", + rb_name.c_str(), + ret.second->N_bytes, + tx_sdu_queue.size()); + } + } else { + log->warning("NULL SDU pointer in write_sdu()\n"); + } +} + +int rlc_um_base::rlc_um_base_tx::build_data_pdu(uint8_t* payload, uint32_t nof_bytes) +{ + unique_byte_buffer_t pdu; + { + std::lock_guard lock(mutex); + log->debug("MAC opportunity - %d bytes\n", nof_bytes); + + if (!tx_sdu && tx_sdu_queue.size() == 0) { + log->info("No data available to be sent\n"); + return 0; + } + + pdu = allocate_unique_buffer(*pool); + if (!pdu || pdu->N_bytes != 0) { + log->error("Failed to allocate PDU buffer\n"); + return 0; + } + } + return build_data_pdu(std::move(pdu), payload, nof_bytes); +} + +} // namespace srslte diff --git a/lib/src/upper/rlc_um_lte.cc b/lib/src/upper/rlc_um_lte.cc new file mode 100644 index 000000000..b1f47603e --- /dev/null +++ b/lib/src/upper/rlc_um_lte.cc @@ -0,0 +1,815 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "srslte/upper/rlc_um_lte.h" +#include + +#define RX_MOD_BASE(x) (((x)-vr_uh - cfg.um.rx_window_size) % cfg.um.rx_mod) + +using namespace asn1::rrc; + +namespace srslte { + +rlc_um_lte::rlc_um_lte(srslte::log* log_, + uint32_t lcid_, + srsue::pdcp_interface_rlc* pdcp_, + srsue::rrc_interface_rlc* rrc_, + srslte::timer_handler* timers_) : + rlc_um_base(log_, lcid_, pdcp_, rrc_, timers_) +{ +} + +// Warning: must call stop() to properly deallocate all buffers +rlc_um_lte::~rlc_um_lte() +{ + stop(); +} + +bool rlc_um_lte::configure(rlc_config_t cnfg_) +{ + // determine bearer name and configure Rx/Tx objects + rb_name = get_rb_name(rrc, lcid, cnfg_.um.is_mrb); + + // store config + cfg = cnfg_; + + rx.reset(new rlc_um_lte_rx(this)); + if (not rx->configure()) { + return false; + } + + tx.reset(new rlc_um_lte_tx(this)); + if (not tx->configure(cfg, rb_name)) { + return false; + } + + log->info("%s configured in %s: t_reordering=%d ms, rx_sn_field_length=%u bits, tx_sn_field_length=%u bits\n", + rb_name.c_str(), + srslte::to_string(cnfg_.rlc_mode).c_str(), + cfg.um.t_reordering, + srslte::to_number(cfg.um.rx_sn_field_length), + srslte::to_number(cfg.um.tx_sn_field_length)); + + rx_enabled = true; + tx_enabled = true; + + return true; +} + +/**************************************************************************** + * Tx Subclass implementation for LTE + ***************************************************************************/ + +rlc_um_lte::rlc_um_lte_tx::rlc_um_lte_tx(rlc_um_base* parent_) : rlc_um_base_tx(parent_) {} + +uint32_t rlc_um_lte::rlc_um_lte_tx::get_buffer_state() +{ + std::lock_guard lock(mutex); + + // Bytes needed for tx SDUs + uint32_t n_sdus = tx_sdu_queue.size(); + uint32_t n_bytes = tx_sdu_queue.size_bytes(); + if (tx_sdu) { + n_sdus++; + n_bytes += tx_sdu->N_bytes; + } + + // Room needed for header extensions? (integer rounding) + if (n_sdus > 1) { + n_bytes += ((n_sdus - 1) * 1.5) + 0.5; + } + + // Room needed for fixed header? + if (n_bytes > 0) + n_bytes += (cfg.um.is_mrb) ? 2 : 3; + + return n_bytes; +} + +bool rlc_um_lte::rlc_um_lte_tx::configure(rlc_config_t cnfg_, std::string rb_name_) +{ + cfg = cnfg_; + + if (cfg.um.tx_mod == 0) { + log->error("Error configuring %s RLC UM: tx_mod==0\n", rb_name.c_str()); + return false; + } + + tx_sdu_queue.resize(cnfg_.tx_queue_length); + + rb_name = rb_name_; + + return true; +} + +int rlc_um_lte::rlc_um_lte_tx::build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes) +{ + std::lock_guard lock(mutex); + rlc_umd_pdu_header_t header; + header.fi = RLC_FI_FIELD_START_AND_END_ALIGNED; + header.sn = vt_us; + header.N_li = 0; + header.sn_size = cfg.um.tx_sn_field_length; + + uint32_t to_move = 0; + uint32_t last_li = 0; + uint8_t* pdu_ptr = pdu->msg; + + int head_len = rlc_um_packed_length(&header); + int pdu_space = SRSLTE_MIN(nof_bytes, pdu->get_tailroom()); + + if (pdu_space <= head_len + 1) { + log->warning("%s Cannot build a PDU - %d bytes available, %d bytes required for header\n", + rb_name.c_str(), + nof_bytes, + head_len); + return 0; + } + + // Check for SDU segment + if (tx_sdu) { + uint32_t space = pdu_space - head_len; + to_move = space >= tx_sdu->N_bytes ? tx_sdu->N_bytes : space; + log->debug( + "%s adding remainder of SDU segment - %d bytes of %d remaining\n", rb_name.c_str(), to_move, tx_sdu->N_bytes); + memcpy(pdu_ptr, tx_sdu->msg, to_move); + last_li = to_move; + pdu_ptr += to_move; + pdu->N_bytes += to_move; + tx_sdu->N_bytes -= to_move; + tx_sdu->msg += to_move; + if (tx_sdu->N_bytes == 0) { + log->debug( + "%s Complete SDU scheduled for tx. Stack latency: %ld us\n", rb_name.c_str(), tx_sdu->get_latency_us()); + + tx_sdu.reset(); + } + pdu_space -= SRSLTE_MIN(to_move, pdu->get_tailroom()); + header.fi |= RLC_FI_FIELD_NOT_START_ALIGNED; // First byte does not correspond to first byte of SDU + } + + // Pull SDUs from queue + while (pdu_space > head_len + 1 && tx_sdu_queue.size() > 0) { + log->debug("pdu_space=%d, head_len=%d\n", pdu_space, head_len); + if (last_li > 0) + header.li[header.N_li++] = last_li; + head_len = rlc_um_packed_length(&header); + tx_sdu = tx_sdu_queue.read(); + uint32_t space = pdu_space - head_len; + to_move = space >= tx_sdu->N_bytes ? tx_sdu->N_bytes : space; + log->debug("%s adding new SDU segment - %d bytes of %d remaining\n", rb_name.c_str(), to_move, tx_sdu->N_bytes); + memcpy(pdu_ptr, tx_sdu->msg, to_move); + last_li = to_move; + pdu_ptr += to_move; + pdu->N_bytes += to_move; + tx_sdu->N_bytes -= to_move; + tx_sdu->msg += to_move; + if (tx_sdu->N_bytes == 0) { + log->debug( + "%s Complete SDU scheduled for tx. Stack latency: %ld us\n", rb_name.c_str(), tx_sdu->get_latency_us()); + + tx_sdu.reset(); + } + pdu_space -= to_move; + } + + if (tx_sdu) { + header.fi |= RLC_FI_FIELD_NOT_END_ALIGNED; // Last byte does not correspond to last byte of SDU + } + + // Set SN + header.sn = vt_us; + vt_us = (vt_us + 1) % cfg.um.tx_mod; + + // Add header and TX + rlc_um_write_data_pdu_header(&header, pdu.get()); + memcpy(payload, pdu->msg, pdu->N_bytes); + uint32_t ret = pdu->N_bytes; + + log->info_hex(payload, ret, "%s Tx PDU SN=%d (%d B)\n", rb_name.c_str(), header.sn, pdu->N_bytes); + + debug_state(); + + return ret; +} + +void rlc_um_lte::rlc_um_lte_tx::debug_state() +{ + log->debug("%s vt_us = %d\n", rb_name.c_str(), vt_us); +} + +/**************************************************************************** + * Rx subclass implementation + ***************************************************************************/ + +rlc_um_lte::rlc_um_lte_rx::rlc_um_lte_rx(rlc_um_base* parent_) : rlc_um_base_rx(parent_), reordering_timer(timers->get_unique_timer()) +{ +} + +rlc_um_lte::rlc_um_lte_rx::~rlc_um_lte_rx() +{ +} + +bool rlc_um_lte::rlc_um_lte_rx::configure() +{ + if (cfg.um.rx_mod == 0) { + log->error("Error configuring %s RLC UM: rx_mod==0\n", rb_name.c_str()); + return false; + } + + // check timer + if (not reordering_timer.is_valid()) { + log->error("Configuring RLC UM RX: timers not configured\n"); + return false; + } + + // configure timer + if (cfg.um.t_reordering > 0) { + reordering_timer.set(static_cast(cfg.um.t_reordering), [this](uint32_t tid) { timer_expired(tid); }); + } + + return true; +} + +void rlc_um_lte::rlc_um_lte_rx::reestablish() +{ + // try to reassemble any SDUs if possible + if (reordering_timer.is_valid() && reordering_timer.is_running()) { + reordering_timer.stop(); + timer_expired(reordering_timer.id()); + } + + std::lock_guard lock(mutex); + reset(); +} + +void rlc_um_lte::rlc_um_lte_rx::stop() +{ + std::lock_guard lock(mutex); + reset(); + + reordering_timer.stop(); +} + +void rlc_um_lte::rlc_um_lte_rx::reset() +{ + vr_ur = 0; + vr_ux = 0; + vr_uh = 0; + pdu_lost = false; + + rx_sdu.reset(); + + // Drop all messages in RX window + rx_window.clear(); +} + +void rlc_um_lte::rlc_um_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) +{ + std::lock_guard lock(mutex); + + rlc_umd_pdu_header_t header; + rlc_um_read_data_pdu_header(payload, nof_bytes, cfg.um.rx_sn_field_length, &header); + log->info_hex(payload, nof_bytes, "RX %s Rx data PDU SN: %d (%d B)", rb_name.c_str(), header.sn, nof_bytes); + + if (RX_MOD_BASE(header.sn) >= RX_MOD_BASE(vr_uh - cfg.um.rx_window_size) && + RX_MOD_BASE(header.sn) < RX_MOD_BASE(vr_ur)) { + log->info("%s SN: %d outside rx window [%d:%d] - discarding\n", rb_name.c_str(), header.sn, vr_ur, vr_uh); + return; + } + + std::map::iterator it = rx_window.find(header.sn); + if (rx_window.end() != it) { + log->info("%s Discarding duplicate SN: %d\n", rb_name.c_str(), header.sn); + return; + } + + // Write to rx window + rlc_umd_pdu_t pdu = {}; + pdu.buf = allocate_unique_buffer(*pool); + if (!pdu.buf) { + log->error("Discarting packet: no space in buffer pool\n"); + return; + } + memcpy(pdu.buf->msg, payload, nof_bytes); + pdu.buf->N_bytes = nof_bytes; + // Strip header from PDU + int header_len = rlc_um_packed_length(&header); + pdu.buf->msg += header_len; + pdu.buf->N_bytes -= header_len; + pdu.header = header; + rx_window[header.sn] = std::move(pdu); + + // Update vr_uh + if (!inside_reordering_window(header.sn)) { + vr_uh = (header.sn + 1) % cfg.um.rx_mod; + } + + // Reassemble and deliver SDUs, while updating vr_ur + log->debug("Entering Reassemble from received PDU\n"); + reassemble_rx_sdus(); + log->debug("Finished reassemble from received PDU\n"); + + // Update reordering variables and timers + if (reordering_timer.is_running()) { + if (RX_MOD_BASE(vr_ux) <= RX_MOD_BASE(vr_ur) || (!inside_reordering_window(vr_ux) && vr_ux != vr_uh)) { + reordering_timer.stop(); + } + } + if (!reordering_timer.is_running()) { + if (RX_MOD_BASE(vr_uh) > RX_MOD_BASE(vr_ur)) { + reordering_timer.run(); + vr_ux = vr_uh; + } + } + + debug_state(); +} + +// No locking required as only called from within handle_data_pdu and timer_expired which lock +void rlc_um_lte::rlc_um_lte_rx::reassemble_rx_sdus() +{ + if (!rx_sdu) { + rx_sdu = allocate_unique_buffer(*pool); + if (!rx_sdu) { + log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); + return; + } + } + + // First catch up with lower edge of reordering window + while (!inside_reordering_window(vr_ur)) { + log->debug("SN=%d is not inside reordering windows\n", vr_ur); + + if (rx_window.end() == rx_window.find(vr_ur)) { + log->debug("SN=%d not in rx_window. Reset received SDU\n", vr_ur); + rx_sdu->clear(); + } else { + // Handle any SDU segments + for (uint32_t i = 0; i < rx_window[vr_ur].header.N_li; i++) { + int len = rx_window[vr_ur].header.li[i]; + log->debug_hex(rx_window[vr_ur].buf->msg, + len, + "Handling segment %d/%d of length %d B of SN=%d\n", + i + 1, + rx_window[vr_ur].header.N_li, + len, + vr_ur); + // Check if we received a middle or end segment + if (rx_sdu->N_bytes == 0 && i == 0 && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { + log->warning("Dropping PDU %d due to lost start segment\n", vr_ur); + // Advance data pointers and continue with next segment + rx_window[vr_ur].buf->msg += len; + rx_window[vr_ur].buf->N_bytes -= len; + rx_sdu->clear(); + break; + } + + memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, len); + rx_sdu->N_bytes += len; + rx_window[vr_ur].buf->msg += len; + rx_window[vr_ur].buf->N_bytes -= len; + if ((pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) || + (vr_ur != ((vr_ur_in_rx_sdu + 1) % cfg.um.rx_mod))) { + log->warning("Dropping remainder of lost PDU (lower edge middle segments, vr_ur=%d, vr_ur_in_rx_sdu=%d)\n", + vr_ur, + vr_ur_in_rx_sdu); + rx_sdu->clear(); + } else { + log->info_hex(rx_sdu->msg, + rx_sdu->N_bytes, + "%s Rx SDU vr_ur=%d, i=%d (lower edge middle segments)", + rb_name.c_str(), + vr_ur, + i); + rx_sdu->set_timestamp(); + if (cfg.um.is_mrb) { + pdcp->write_pdu_mch(lcid, std::move(rx_sdu)); + } else { + pdcp->write_pdu(lcid, std::move(rx_sdu)); + } + rx_sdu = allocate_unique_buffer(*pool); + if (!rx_sdu) { + log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); + return; + } + } + pdu_lost = false; + } + + // Handle last segment + if (rx_sdu->N_bytes > 0 || rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { + log->info("Writing last segment in SDU buffer. Lower edge vr_ur=%d, Buffer size=%d, segment size=%d\n", + vr_ur, + rx_sdu->N_bytes, + rx_window[vr_ur].buf->N_bytes); + + memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, rx_window[vr_ur].buf->N_bytes); + rx_sdu->N_bytes += rx_window[vr_ur].buf->N_bytes; + vr_ur_in_rx_sdu = vr_ur; + if (rlc_um_end_aligned(rx_window[vr_ur].header.fi)) { + if (pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { + log->warning("Dropping remainder of lost PDU (lower edge last segments)\n"); + rx_sdu->clear(); + } else { + log->info_hex( + rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (lower edge last segments)", rb_name.c_str(), vr_ur); + rx_sdu->set_timestamp(); + if (cfg.um.is_mrb) { + pdcp->write_pdu_mch(lcid, std::move(rx_sdu)); + } else { + pdcp->write_pdu(lcid, std::move(rx_sdu)); + } + rx_sdu = allocate_unique_buffer(*pool); + if (!rx_sdu) { + log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); + return; + } + } + pdu_lost = false; + } + } + + // Clean up rx_window + rx_window.erase(vr_ur); + } + + vr_ur = (vr_ur + 1) % cfg.um.rx_mod; + } + + // Now update vr_ur until we reach an SN we haven't yet received + while (rx_window.end() != rx_window.find(vr_ur)) { + log->debug("Reassemble loop for vr_ur=%d\n", vr_ur); + + if (not pdu_belongs_to_rx_sdu()) { + log->warning( + "PDU SN=%d lost, stop reassambling SDU (vr_ur_in_rx_sdu=%d)\n", vr_ur_in_rx_sdu + 1, vr_ur_in_rx_sdu); + pdu_lost = false; // Reset flag to not prevent reassembling of further segments + rx_sdu->clear(); + } + + // Handle any SDU segments + for (uint32_t i = 0; i < rx_window[vr_ur].header.N_li; i++) { + uint16_t len = rx_window[vr_ur].header.li[i]; + log->debug("Handling SDU segment i=%d with len=%d of vr_ur=%d N_li=%d [%s]\n", + i, + len, + vr_ur, + rx_window[vr_ur].header.N_li, + rlc_fi_field_text[rx_window[vr_ur].header.fi]); + // Check if the first part of the PDU is a middle or end segment + if (rx_sdu->N_bytes == 0 && i == 0 && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { + log->warning_hex( + rx_window[vr_ur].buf->msg, len, "Dropping first %d B of SN %d due to lost start segment\n", len, vr_ur); + + if (rx_window[vr_ur].buf->N_bytes < len) { + log->error("Dropping remaining remainder of SN %d too (N_bytes=%u < len=%d)\n", + vr_ur, + rx_window[vr_ur].buf->N_bytes, + len); + goto clean_up_rx_window; + } + + // Advance data pointers and continue with next segment + rx_window[vr_ur].buf->msg += len; + rx_window[vr_ur].buf->N_bytes -= len; + rx_sdu->clear(); + + // Reset flag, it is safe to process all remaining segments of this PDU + pdu_lost = false; + continue; + } + + // Check available space in SDU + if ((uint32_t)len > rx_sdu->get_tailroom()) { + log->error("Dropping PDU %d due to buffer mis-alignment (current segment len %d B, received %d B)\n", + vr_ur, + rx_sdu->N_bytes, + len); + rx_sdu->clear(); + goto clean_up_rx_window; + } + + if (not pdu_belongs_to_rx_sdu()) { + log->info_hex(rx_window[vr_ur].buf->msg, len, "Copying first %d bytes of new SDU\n", len); + log->info("Updating vr_ur_in_rx_sdu. old=%d, new=%d\n", vr_ur_in_rx_sdu, vr_ur); + vr_ur_in_rx_sdu = vr_ur; + } else { + log->info_hex(rx_window[vr_ur].buf->msg, + len, + "Concatenating %d bytes in to current length %d. rx_window remaining bytes=%d, " + "vr_ur_in_rx_sdu=%d, vr_ur=%d, rx_mod=%d, last_mod=%d\n", + len, + rx_sdu->N_bytes, + rx_window[vr_ur].buf->N_bytes, + vr_ur_in_rx_sdu, + vr_ur, + cfg.um.rx_mod, + (vr_ur_in_rx_sdu + 1) % cfg.um.rx_mod); + } + + memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, len); + rx_sdu->N_bytes += len; + rx_window[vr_ur].buf->msg += len; + rx_window[vr_ur].buf->N_bytes -= len; + vr_ur_in_rx_sdu = vr_ur; + + if (pdu_belongs_to_rx_sdu()) { + log->info_hex(rx_sdu->msg, + rx_sdu->N_bytes, + "%s Rx SDU vr_ur=%d, i=%d, (update vr_ur middle segments)", + rb_name.c_str(), + vr_ur, + i); + rx_sdu->set_timestamp(); + if (cfg.um.is_mrb) { + pdcp->write_pdu_mch(lcid, std::move(rx_sdu)); + } else { + pdcp->write_pdu(lcid, std::move(rx_sdu)); + } + rx_sdu = allocate_unique_buffer(*pool); + if (!rx_sdu) { + log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); + return; + } + } else { + log->warning("Dropping remainder of lost PDU (update vr_ur middle segments, vr_ur=%d, vr_ur_in_rx_sdu=%d)\n", + vr_ur, + vr_ur_in_rx_sdu); + // Advance data pointers and continue with next segment + rx_window[vr_ur].buf->msg += len; + rx_window[vr_ur].buf->N_bytes -= len; + } + pdu_lost = false; + } + + // Handle last segment + if (rx_sdu->N_bytes == 0 && rx_window[vr_ur].header.N_li == 0 && + !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { + log->warning("Dropping PDU %d due to lost start segment\n", vr_ur); + rx_sdu->clear(); + goto clean_up_rx_window; + } + + if (rx_sdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES && + rx_window[vr_ur].buf->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES && + rx_window[vr_ur].buf->N_bytes + rx_sdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES) { + log->info_hex(rx_window[vr_ur].buf->msg, + rx_window[vr_ur].buf->N_bytes, + "Writing last segment in SDU buffer. Updating vr_ur=%d, vr_ur_in_rx_sdu=%d, Buffer size=%d, " + "segment size=%d\n", + vr_ur, + vr_ur_in_rx_sdu, + rx_sdu->N_bytes, + rx_window[vr_ur].buf->N_bytes); + memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, rx_window[vr_ur].buf->N_bytes); + rx_sdu->N_bytes += rx_window[vr_ur].buf->N_bytes; + } else { + log->error("Out of bounds while reassembling SDU buffer in UM: sdu_len=%d, window_buffer_len=%d, vr_ur=%d\n", + rx_sdu->N_bytes, + rx_window[vr_ur].buf->N_bytes, + vr_ur); + } + vr_ur_in_rx_sdu = vr_ur; + if (rlc_um_end_aligned(rx_window[vr_ur].header.fi)) { + if (pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { + log->warning("Dropping remainder of lost PDU (update vr_ur last segments)\n"); + rx_sdu->clear(); + } else { + log->info_hex( + rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (update vr_ur last segments)", rb_name.c_str(), vr_ur); + rx_sdu->set_timestamp(); + if (cfg.um.is_mrb) { + pdcp->write_pdu_mch(lcid, std::move(rx_sdu)); + } else { + pdcp->write_pdu(lcid, std::move(rx_sdu)); + } + rx_sdu = allocate_unique_buffer(*pool); + if (!rx_sdu) { + log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); + return; + } + } + pdu_lost = false; + } + + clean_up_rx_window: + // Clean up rx_window + rx_window.erase(vr_ur); + + vr_ur = (vr_ur + 1) % cfg.um.rx_mod; + } +} + +// Only called when lock is hold +bool rlc_um_lte::rlc_um_lte_rx::pdu_belongs_to_rx_sdu() +{ + // return true if the currently received SDU + if (((vr_ur_in_rx_sdu + 1) % cfg.um.rx_mod == vr_ur) || (vr_ur == vr_ur_in_rx_sdu)) { + return true; + } + return false; +} + +// Only called when lock is hold +// 36.322 Section 5.1.2.2.1 +bool rlc_um_lte::rlc_um_lte_rx::inside_reordering_window(uint16_t sn) +{ + if (cfg.um.rx_window_size == 0 || rx_window.empty()) { + return true; + } + if (RX_MOD_BASE(vr_uh - cfg.um.rx_window_size) <= RX_MOD_BASE(sn) && RX_MOD_BASE(sn) < RX_MOD_BASE(vr_uh)) { + return true; + } else { + return false; + } +} + +/**************************************************************************** + * Timeout callback interface + ***************************************************************************/ + +void rlc_um_lte::rlc_um_lte_rx::timer_expired(uint32_t timeout_id) +{ + std::lock_guard lock(mutex); + if (reordering_timer.id() == timeout_id) { + // 36.322 v10 Section 5.1.2.2.4 + log->info("%s reordering timeout expiry - updating vr_ur and reassembling\n", rb_name.c_str()); + + log->warning("Lost PDU SN: %d\n", vr_ur); + + pdu_lost = true; + if (rx_sdu != NULL) { + rx_sdu->clear(); + } + + while (RX_MOD_BASE(vr_ur) < RX_MOD_BASE(vr_ux)) { + vr_ur = (vr_ur + 1) % cfg.um.rx_mod; + log->debug("Entering Reassemble from timeout id=%d\n", timeout_id); + reassemble_rx_sdus(); + log->debug("Finished reassemble from timeout id=%d\n", timeout_id); + } + + if (RX_MOD_BASE(vr_uh) > RX_MOD_BASE(vr_ur)) { + reordering_timer.run(); + vr_ux = vr_uh; + } + + debug_state(); + } +} + +/**************************************************************************** + * Helper functions + ***************************************************************************/ + +void rlc_um_lte::rlc_um_lte_rx::debug_state() +{ + log->debug("%s vr_ur = %d, vr_ux = %d, vr_uh = %d\n", rb_name.c_str(), vr_ur, vr_ux, vr_uh); +} + +/**************************************************************************** + * Header pack/unpack helper functions + * Ref: 3GPP TS 36.322 v10.0.0 Section 6.2.1 + ***************************************************************************/ + +void rlc_um_read_data_pdu_header(byte_buffer_t* pdu, rlc_umd_sn_size_t sn_size, rlc_umd_pdu_header_t* header) +{ + rlc_um_read_data_pdu_header(pdu->msg, pdu->N_bytes, sn_size, header); +} + +void rlc_um_read_data_pdu_header(uint8_t* payload, + uint32_t nof_bytes, + rlc_umd_sn_size_t sn_size, + rlc_umd_pdu_header_t* header) +{ + uint8_t ext; + uint8_t* ptr = payload; + // Fixed part + if (sn_size == rlc_umd_sn_size_t::size5bits) { + header->fi = (rlc_fi_field_t)((*ptr >> 6) & 0x03); // 2 bits FI + ext = ((*ptr >> 5) & 0x01); // 1 bit EXT + header->sn = *ptr & 0x1F; // 5 bits SN + ptr++; + } else { + header->fi = (rlc_fi_field_t)((*ptr >> 3) & 0x03); // 2 bits FI + ext = ((*ptr >> 2) & 0x01); // 1 bit EXT + header->sn = (*ptr & 0x03) << 8; // 2 bits SN + ptr++; + header->sn |= (*ptr & 0xFF); // 8 bits SN + ptr++; + } + + header->sn_size = sn_size; + + // Extension part + header->N_li = 0; + while (ext) { + if (header->N_li % 2 == 0) { + ext = ((*ptr >> 7) & 0x01); + header->li[header->N_li] = (*ptr & 0x7F) << 4; // 7 bits of LI + ptr++; + header->li[header->N_li] |= (*ptr & 0xF0) >> 4; // 4 bits of LI + header->N_li++; + } else { + ext = (*ptr >> 3) & 0x01; + header->li[header->N_li] = (*ptr & 0x07) << 8; // 3 bits of LI + ptr++; + header->li[header->N_li] |= (*ptr & 0xFF); // 8 bits of LI + header->N_li++; + ptr++; + } + } +} + +void rlc_um_write_data_pdu_header(rlc_umd_pdu_header_t* header, byte_buffer_t* pdu) +{ + uint32_t i; + uint8_t ext = (header->N_li > 0) ? 1 : 0; + // Make room for the header + uint32_t len = rlc_um_packed_length(header); + pdu->msg -= len; + uint8_t* ptr = pdu->msg; + + // Fixed part + if (header->sn_size == rlc_umd_sn_size_t::size5bits) { + *ptr = (header->fi & 0x03) << 6; // 2 bits FI + *ptr |= (ext & 0x01) << 5; // 1 bit EXT + *ptr |= header->sn & 0x1F; // 5 bits SN + ptr++; + } else { + *ptr = (header->fi & 0x03) << 3; // 3 Reserved bits | 2 bits FI + *ptr |= (ext & 0x01) << 2; // 1 bit EXT + *ptr |= (header->sn & 0x300) >> 8; // 2 bits SN + ptr++; + *ptr = (header->sn & 0xFF); // 8 bits SN + ptr++; + } + + // Extension part + i = 0; + while (i < header->N_li) { + ext = ((i + 1) == header->N_li) ? 0 : 1; + *ptr = (ext & 0x01) << 7; // 1 bit header + *ptr |= (header->li[i] & 0x7F0) >> 4; // 7 bits of LI + ptr++; + *ptr = (header->li[i] & 0x00F) << 4; // 4 bits of LI + i++; + if (i < header->N_li) { + ext = ((i + 1) == header->N_li) ? 0 : 1; + *ptr |= (ext & 0x01) << 3; // 1 bit header + *ptr |= (header->li[i] & 0x700) >> 8; // 3 bits of LI + ptr++; + *ptr = (header->li[i] & 0x0FF); // 8 bits of LI + ptr++; + i++; + } + } + // Pad if N_li is odd + if (header->N_li % 2 == 1) + ptr++; + + pdu->N_bytes += ptr - pdu->msg; +} + +uint32_t rlc_um_packed_length(rlc_umd_pdu_header_t* header) +{ + uint32_t len = 0; + if (header->sn_size == rlc_umd_sn_size_t::size5bits) { + len += 1; // Fixed part is 1 byte + } else { + len += 2; // Fixed part is 2 bytes + } + len += header->N_li * 1.5 + 0.5; // Extension part - integer rounding up + return len; +} + +bool rlc_um_start_aligned(uint8_t fi) +{ + return (fi == RLC_FI_FIELD_START_AND_END_ALIGNED || fi == RLC_FI_FIELD_NOT_END_ALIGNED); +} + +bool rlc_um_end_aligned(uint8_t fi) +{ + return (fi == RLC_FI_FIELD_START_AND_END_ALIGNED || fi == RLC_FI_FIELD_NOT_START_ALIGNED); +} + +} // namespace srslte diff --git a/lib/src/upper/rlc_um_nr.cc b/lib/src/upper/rlc_um_nr.cc new file mode 100644 index 000000000..09f4c6986 --- /dev/null +++ b/lib/src/upper/rlc_um_nr.cc @@ -0,0 +1,688 @@ +/* + * Copyright 2013-2019 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "srslte/upper/rlc_um_nr.h" +#include + +#define RX_MOD_NR_BASE(x) (((x)-RX_Next_Highest - cfg.um_nr.UM_Window_Size) % cfg.um_nr.mod) + +using namespace asn1::rrc; + +namespace srslte { + +rlc_um_nr::rlc_um_nr(srslte::log* log_, + uint32_t lcid_, + srsue::pdcp_interface_rlc* pdcp_, + srsue::rrc_interface_rlc* rrc_, + srslte::timer_handler* timers_) : + rlc_um_base(log_, lcid_, pdcp_, rrc_, timers_) +{ +} + +rlc_um_nr::~rlc_um_nr() +{ + stop(); +} + +bool rlc_um_nr::configure(rlc_config_t cnfg_) +{ + // determine bearer name and configure Rx/Tx objects + rb_name = get_rb_name(rrc, lcid, cnfg_.um.is_mrb); + + // store config + cfg = cnfg_; + + rx.reset(new rlc_um_nr_rx(this)); + if (not rx->configure()) { + return false; + } + + tx.reset(new rlc_um_nr_tx(this)); + if (not tx->configure(cfg, rb_name)) { + return false; + } + + log->info("%s configured in %s: sn_field_length=%u bits\n", + rb_name.c_str(), + srslte::to_string(cnfg_.rlc_mode).c_str(), + srslte::to_number(cfg.um_nr.sn_field_length)); + + rx_enabled = true; + tx_enabled = true; + + return true; +} + +/**************************************************************************** + * Tx Subclass implementation + ***************************************************************************/ + +rlc_um_nr::rlc_um_nr_tx::rlc_um_nr_tx(rlc_um_base* parent_) : rlc_um_base_tx(parent_) {} + +uint32_t rlc_um_nr::rlc_um_nr_tx::get_buffer_state() +{ + std::lock_guard lock(mutex); + + // Bytes needed for tx SDUs + uint32_t n_sdus = tx_sdu_queue.size(); + uint32_t n_bytes = tx_sdu_queue.size_bytes(); + if (tx_sdu) { + n_sdus++; + n_bytes += tx_sdu->N_bytes; + } + + // Room needed for header extensions? (integer rounding) + if (n_sdus > 1) { + n_bytes += ((n_sdus - 1) * 1.5) + 0.5; + } + + // Room needed for fixed header? + if (n_bytes > 0) + n_bytes += (cfg.um.is_mrb) ? 2 : 3; + + return n_bytes; +} + +bool rlc_um_nr::rlc_um_nr_tx::configure(rlc_config_t cnfg_, std::string rb_name_) +{ + cfg = cnfg_; + + if (cfg.um_nr.mod == 0) { + log->error("Error configuring %s RLC UM: tx_mod==0\n", rb_name.c_str()); + return false; + } + + tx_sdu_queue.resize(cnfg_.tx_queue_length); + + rb_name = rb_name_; + + return true; +} + +int rlc_um_nr::rlc_um_nr_tx::build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes) +{ + std::lock_guard lock(mutex); + rlc_um_nr_pdu_header_t header = {}; + header.si = rlc_nr_si_field_t::full_sdu; + header.sn = TX_Next; + header.sn_size = cfg.um_nr.sn_field_length; + + uint32_t to_move = 0; + uint8_t* pdu_ptr = pdu->msg; + + int head_len = rlc_um_nr_packed_length(header); + int pdu_space = SRSLTE_MIN(nof_bytes, pdu->get_tailroom()); + + if (pdu_space <= head_len + 1) { + log->warning("%s Cannot build a PDU - %d bytes available, %d bytes required for header\n", + rb_name.c_str(), + nof_bytes, + head_len); + return 0; + } + + // Check for SDU segment + if (tx_sdu) { + uint32_t space = pdu_space - head_len; + to_move = space >= tx_sdu->N_bytes ? tx_sdu->N_bytes : space; + log->debug( + "%s adding remainder of SDU segment - %d bytes of %d remaining\n", rb_name.c_str(), to_move, tx_sdu->N_bytes); + memcpy(pdu_ptr, tx_sdu->msg, to_move); + pdu_ptr += to_move; + pdu->N_bytes += to_move; + tx_sdu->N_bytes -= to_move; + tx_sdu->msg += to_move; + if (tx_sdu->N_bytes == 0) { + log->debug( + "%s Complete SDU scheduled for tx. Stack latency: %ld us\n", rb_name.c_str(), tx_sdu->get_latency_us()); + tx_sdu.reset(); + header.si = rlc_nr_si_field_t::last_segment; + } else { + header.si = rlc_nr_si_field_t::neither_first_nor_last_segment; + } + pdu_space -= SRSLTE_MIN(to_move, pdu->get_tailroom()); + header.so = next_so; + } else { + // Pull SDU from queue + log->debug("pdu_space=%d, head_len=%d\n", pdu_space, head_len); + + head_len = rlc_um_nr_packed_length(header); + tx_sdu = tx_sdu_queue.read(); + uint32_t space = pdu_space - head_len; + to_move = space >= tx_sdu->N_bytes ? tx_sdu->N_bytes : space; + log->debug("%s adding new SDU - %d bytes of %d remaining\n", rb_name.c_str(), to_move, tx_sdu->N_bytes); + memcpy(pdu_ptr, tx_sdu->msg, to_move); + pdu_ptr += to_move; + pdu->N_bytes += to_move; + tx_sdu->N_bytes -= to_move; + tx_sdu->msg += to_move; + if (tx_sdu->N_bytes == 0) { + log->debug( + "%s Complete SDU scheduled for tx. Stack latency: %ld us\n", rb_name.c_str(), tx_sdu->get_latency_us()); + tx_sdu.reset(); + header.si = rlc_nr_si_field_t::full_sdu; + } else { + header.si = rlc_nr_si_field_t::first_segment; + } + pdu_space -= to_move; + } + + // advance SO offset + next_so += to_move; + + // Update SN if needed + if (header.si == rlc_nr_si_field_t::last_segment) { + TX_Next = (TX_Next + 1) % cfg.um_nr.mod; + next_so = 0; + } + + // Add header and TX + rlc_um_nr_write_data_pdu_header(header, pdu.get()); + memcpy(payload, pdu->msg, pdu->N_bytes); + uint32_t ret = pdu->N_bytes; + + log->info_hex(payload, ret, "%s Tx PDU SN=%d (%d B)\n", rb_name.c_str(), header.sn, pdu->N_bytes); + + debug_state(); + + return ret; +} + +void rlc_um_nr::rlc_um_nr_tx::debug_state() +{ + log->debug("%s TX_Next = %d\n", rb_name.c_str(), TX_Next); +} + +/**************************************************************************** + * Rx Subclass implementation + ***************************************************************************/ + +rlc_um_nr::rlc_um_nr_rx::rlc_um_nr_rx(rlc_um_base* parent_) : + rlc_um_base_rx(parent_), reassembly_timer(timers->get_unique_timer()) +{ +} + +bool rlc_um_nr::rlc_um_nr_rx::configure() +{ + if (cfg.um_nr.mod == 0) { + log->error("Error configuring %s RLC UM: rx_mod==0\n", rb_name.c_str()); + return false; + } + + // check timer + if (not reassembly_timer.is_valid()) { + log->error("Configuring RLC UM NR RX: timers not configured\n"); + return false; + } + + // configure timer + if (cfg.um_nr.t_reassembly_ms > 0) { + reassembly_timer.set(static_cast(cfg.um_nr.t_reassembly_ms), + [this](uint32_t tid) { timer_expired(tid); }); + } + + return true; +} + +void rlc_um_nr::rlc_um_nr_rx::stop() +{ + std::lock_guard lock(mutex); + reset(); + reassembly_timer.stop(); +} + +void rlc_um_nr::rlc_um_nr_rx::reset() +{ + RX_Next_Reassembly = 0; + RX_Timer_Trigger = 0; + RX_Next_Highest = 0; + + rx_sdu.reset(); + + // Drop all messages in RX window + rx_window.clear(); + + // stop timer + if (reassembly_timer.is_valid()) { + reassembly_timer.stop(); + } +} + +// TS 38.322 Sec. 5.1.2 +void rlc_um_nr::rlc_um_nr_rx::reestablish() +{ + // drop all SDUs, SDU segments, PDUs and reset timers + reset(); +} + +// TS 38.322 v15.003 Section 5.2.2.2.4 +void rlc_um_nr::rlc_um_nr_rx::timer_expired(uint32_t timeout_id) +{ + std::lock_guard lock(mutex); + if (reassembly_timer.id() == timeout_id) { + log->info("%s reassembly timeout expiry - updating RX_Next_Reassembly and reassembling\n", rb_name.c_str()); + + log->warning("Lost PDU SN: %d\n", RX_Next_Reassembly); + metrics.num_lost_pdus++; + + if (rx_sdu != nullptr) { + rx_sdu->clear(); + } + + // update RX_Next_Reassembly to the next SN that has not been reassembled yet + RX_Next_Reassembly = RX_Timer_Trigger; + while (RX_MOD_NR_BASE(RX_Next_Reassembly) < RX_MOD_NR_BASE(RX_Next_Highest)) { + RX_Next_Reassembly = (RX_Next_Reassembly + 1) % cfg.um_nr.mod; + debug_state(); + } + + // discard all segments with SN < updated RX_Next_Reassembly + for (auto it = rx_window.begin(); it != rx_window.end();) { + if (it->first < RX_Next_Reassembly) { + it = rx_window.erase(it); + } else { + ++it; + } + } + + // check start of t_reassembly + if (RX_MOD_NR_BASE(RX_Next_Highest) > RX_MOD_NR_BASE(RX_Next_Reassembly + 1) || + (RX_MOD_NR_BASE(RX_Next_Highest) == RX_MOD_NR_BASE(RX_Next_Reassembly + 1) && + has_missing_byte_segment(RX_Next_Reassembly))) { + reassembly_timer.run(); + RX_Timer_Trigger = RX_Next_Highest; + } + + debug_state(); + } +} + +// Sec 5.2.2.2.1 +bool rlc_um_nr::rlc_um_nr_rx::sn_in_reassembly_window(const uint32_t sn) +{ + return (RX_MOD_NR_BASE(RX_Next_Highest - cfg.um_nr.UM_Window_Size) <= RX_MOD_NR_BASE(sn) && + RX_MOD_NR_BASE(sn) < RX_MOD_NR_BASE(RX_Next_Highest)); +} + +// Sec 5.2.2.2.2 +bool rlc_um_nr::rlc_um_nr_rx::sn_invalid_for_rx_buffer(const uint32_t sn) +{ + return (RX_MOD_NR_BASE(RX_Next_Highest - cfg.um_nr.UM_Window_Size) <= RX_MOD_NR_BASE(sn) && + RX_MOD_NR_BASE(sn) < RX_MOD_NR_BASE(RX_Next_Reassembly)); +} + +unique_byte_buffer_t rlc_um_nr::rlc_um_nr_rx::rlc_um_nr_strip_pdu_header(const rlc_um_nr_pdu_header_t& header, + const uint8_t* payload, + const uint32_t nof_bytes) +{ + unique_byte_buffer_t sdu = allocate_unique_buffer(*pool); + if (!sdu) { + log->error("Discarting packet: no space in buffer pool\n"); + return nullptr; + } + memcpy(sdu->msg, payload, nof_bytes); + sdu->N_bytes = nof_bytes; + + // strip RLC header + int header_len = rlc_um_nr_packed_length(header); + sdu->msg += header_len; + sdu->N_bytes -= header_len; + return sdu; +} + +bool rlc_um_nr::rlc_um_nr_rx::has_missing_byte_segment(const uint32_t sn) +{ + // is at least one missing byte segment of the RLC SDU associated with SN = RX_Next_Reassembly before the last byte of + // all received segments of this RLC SDU + // FIXME: check assumption + // if SN can be found in rx_window, it means that at least one segment is missing + return (rx_window.find(sn) != rx_window.end()); +} + +// Sect 5.2.2.2.3 +void rlc_um_nr::rlc_um_nr_rx::handle_rx_buffer_update(const uint32_t sn) +{ + if (rx_window.find(sn) != rx_window.end()) { + // iterate over received segments and try to assemble full SDU + auto& pdu = rx_window.at(sn); + for (auto it = pdu.segments.begin(); it != pdu.segments.end();) { + log->debug("Have %s segment with SO=%d for SN=%d\n", + to_string_short(it->second.header.si).c_str(), + it->second.header.so, + it->second.header.sn); + if (it->second.header.so == pdu.next_expected_so) { + if (pdu.next_expected_so == 0) { + if (pdu.sdu == nullptr) { + // reuse buffer of first segment for final SDU + pdu.sdu = std::move(it->second.buf); + pdu.next_expected_so = pdu.sdu->N_bytes; + log->debug("Reusing first segment of SN=%d for final SDU\n", it->second.header.sn); + it = pdu.segments.erase(it); + } else { + log->debug("SDU buffer already allocated. Possible retransmission of first segment.\n"); + if (it->second.header.so != pdu.next_expected_so) { + log->error("Invalid PDU. SO doesn't match. Discarting all segments of SN=%d.\n", sn); + rx_window.erase(sn); + return; + } + } + } else { + if (it->second.buf->N_bytes > pdu.sdu->get_tailroom()) { + log->error("Cannot fit RLC PDU in SDU buffer (tailroom=%d, len=%d), dropping both. Erasing SN=%d.\n", + rx_sdu->get_tailroom(), + it->second.buf->N_bytes, + it->second.header.sn); + rx_window.erase(sn); + metrics.num_lost_pdus++; + return; + } + + // add this segment to the end of the SDU buffer + memcpy(pdu.sdu->msg + pdu.sdu->N_bytes, it->second.buf->msg, it->second.buf->N_bytes); + pdu.sdu->N_bytes += it->second.buf->N_bytes; + pdu.next_expected_so += it->second.buf->N_bytes; + log->debug("Appended SO=%d of SN=%d\n", it->second.header.so, it->second.header.sn); + it = pdu.segments.erase(it); + + if (pdu.next_expected_so == pdu.total_sdu_length) { + // deliver full SDU to upper layers + log->info("Delivering %s SDU SN=%d (%d B)", rb_name.c_str(), sn, pdu.sdu->N_bytes); + pdcp->write_pdu(lcid, std::move(pdu.sdu)); + + // find next SN in rx buffer + if (sn == RX_Next_Reassembly) { + RX_Next_Reassembly = ((RX_Next_Reassembly + 1) % cfg.um_nr.mod); + while (RX_MOD_NR_BASE(RX_Next_Reassembly) < RX_MOD_NR_BASE(RX_Next_Highest)) { + RX_Next_Reassembly = (RX_Next_Reassembly + 1) % cfg.um_nr.mod; + } + log->debug("Updating RX_Next_Reassembly=%d\n", RX_Next_Reassembly); + } + + // delete PDU from rx_window + rx_window.erase(sn); + return; + } + } + } else { + // handle next segment + ++it; + } + } + + // check for SN outside of rx window + if (not sn_in_reassembly_window(sn)) { + // update RX_Next_highest + RX_Next_Highest = sn + 1; + log->debug("Updating RX_Next_Highest=%d\n", RX_Next_Highest); + + // drop all SNs outside of new rx window + for (auto it = rx_window.begin(); it != rx_window.end();) { + if (not sn_in_reassembly_window(it->first)) { + log->info("%s SN: %d outside rx window [%d:%d] - discarding\n", + rb_name.c_str(), + it->first, + RX_Next_Highest - cfg.um_nr.UM_Window_Size, + RX_Next_Highest); + it = rx_window.erase(it); + metrics.num_lost_pdus++; + } else { + ++it; + } + } + + if (not sn_in_reassembly_window(RX_Next_Reassembly)) { + // update RX_Next_Reassembly to first SN that has not been reassembled and delivered + for (const auto& rx_pdu : rx_window) { + if (rx_pdu.first >= RX_MOD_NR_BASE(RX_Next_Highest - cfg.um_nr.UM_Window_Size)) { + RX_Next_Reassembly = rx_pdu.first; + log->debug("Updating RX_Next_Reassembly=%d\n", RX_Next_Reassembly); + break; + } + } + } + + if (reassembly_timer.is_running()) { + if (RX_Timer_Trigger <= RX_Next_Reassembly || + (not sn_in_reassembly_window(RX_Timer_Trigger) and RX_Timer_Trigger != RX_Next_Highest) || + ((RX_Next_Highest == RX_Next_Reassembly + 1) && not has_missing_byte_segment(sn))) { + reassembly_timer.stop(); + } + } + + if (not reassembly_timer.is_running() && has_missing_byte_segment(sn)) { + if (RX_Next_Highest > RX_Next_Reassembly + 1) { + reassembly_timer.run(); + RX_Timer_Trigger = RX_Next_Highest; + } + } + } + } else { + log->error("SN=%d does not exist in Rx buffer\n", sn); + } +} + +inline void rlc_um_nr::rlc_um_nr_rx::update_total_sdu_length(rlc_umd_pdu_segments_nr_t& pdu_segments, + const rlc_umd_pdu_nr_t& rx_pdu) +{ + if (rx_pdu.header.si == rlc_nr_si_field_t::last_segment) { + pdu_segments.total_sdu_length = rx_pdu.header.so + rx_pdu.buf->N_bytes; + log->info("%s updating total SDU length for SN=%d to %d B\n", + rb_name.c_str(), + rx_pdu.header.sn, + pdu_segments.total_sdu_length); + } +}; + +// Section 5.2.2.2.2 +void rlc_um_nr::rlc_um_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) +{ + std::lock_guard lock(mutex); + + rlc_um_nr_pdu_header_t header = {}; + rlc_um_nr_read_data_pdu_header(payload, nof_bytes, cfg.um_nr.sn_field_length, &header); + log->debug_hex(payload, nof_bytes, "RX %s Rx data PDU (%d B)", rb_name.c_str(), nof_bytes); + + // check if PDU contains a SN + if (header.si == rlc_nr_si_field_t::full_sdu) { + // copy full PDU into buffer + unique_byte_buffer_t sdu = rlc_um_nr_strip_pdu_header(header, payload, nof_bytes); + + // deliver to PDCP + log->info("Delivering %s SDU (%d B)", rb_name.c_str(), sdu->N_bytes); + pdcp->write_pdu(lcid, std::move(sdu)); + } else if (sn_invalid_for_rx_buffer(header.sn)) { + log->info("%s Discarding SN=%d\n", rb_name.c_str(), header.sn); + // Nothing else to do here .. + } else { + // place PDU in receive buffer + rlc_umd_pdu_nr_t rx_pdu = {}; + rx_pdu.header = header; + rx_pdu.buf = rlc_um_nr_strip_pdu_header(header, payload, nof_bytes); + + // check if this SN is already present in rx buffer + if (rx_window.find(header.sn) == rx_window.end()) { + // first received segment of this SN, add to rx buffer + log->info("%s placing %s segment of SN=%d in Rx buffer\n", + rb_name.c_str(), + to_string_short(header.si).c_str(), + header.sn); + rlc_umd_pdu_segments_nr_t pdu_segments = {}; + update_total_sdu_length(pdu_segments, rx_pdu); + pdu_segments.segments.emplace(header.so, std::move(rx_pdu)); + rx_window[header.sn] = std::move(pdu_segments); + } else { + // other segment for this SN already present, update received data + log->info("%s updating SN=%d at SO=%d with %d B\n", + rb_name.c_str(), + rx_pdu.header.sn, + rx_pdu.header.so, + rx_pdu.buf->N_bytes); + + auto& pdu_segments = rx_window.at(header.sn); + + // calculate total SDU length + update_total_sdu_length(pdu_segments, rx_pdu); + + // append to list of segments + pdu_segments.segments.emplace(header.so, std::move(rx_pdu)); + } + + // handle received segments + handle_rx_buffer_update(header.sn); + } + + debug_state(); +} + +void rlc_um_nr::rlc_um_nr_rx::debug_state() +{ + log->debug("%s RX_Next_Reassembly=%d, RX_Timer_Trigger=%d, RX_Next_Highest=%d, t_Reassembly=%s\n", + rb_name.c_str(), + RX_Next_Reassembly, + RX_Timer_Trigger, + RX_Next_Highest, + reassembly_timer.is_running() ? "running" : "stopped"); +} +/**************************************************************************** + * Header pack/unpack helper functions + * Ref: 3GPP TS 38.322 v15.3.0 Section 6.2.2.3 + ***************************************************************************/ + +uint32_t rlc_um_nr_read_data_pdu_header(const byte_buffer_t* pdu, + const rlc_um_nr_sn_size_t sn_size, + rlc_um_nr_pdu_header_t* header) +{ + return rlc_um_nr_read_data_pdu_header(pdu->msg, pdu->N_bytes, sn_size, header); +} + +uint32_t rlc_um_nr_read_data_pdu_header(const uint8_t* payload, + const uint32_t nof_bytes, + const rlc_um_nr_sn_size_t sn_size, + rlc_um_nr_pdu_header_t* header) +{ + uint8_t* ptr = const_cast(payload); + + header->sn_size = sn_size; + + // Fixed part + if (sn_size == rlc_um_nr_sn_size_t::size6bits) { + header->si = (rlc_nr_si_field_t)((*ptr >> 6) & 0x03); // 2 bits SI + header->sn = *ptr & 0x3F; // 6 bits SN + // sanity check + if (header->si == rlc_nr_si_field_t::full_sdu and !(header->sn == 0)) { + fprintf(stderr, "Malformed PDU, reserved bits are set.\n"); + return 0; + } + ptr++; + } else if (sn_size == rlc_um_nr_sn_size_t::size12bits) { + header->si = (rlc_nr_si_field_t)((*ptr >> 6) & 0x03); // 2 bits SI + header->sn = (*ptr & 0x0F) << 4; // 4 bits SN + + // sanity check + if (header->si == rlc_nr_si_field_t::first_segment) { + // make sure two reserved bits are not set + if (((*ptr >> 4) & 0x03) != 0) { + fprintf(stderr, "Malformed PDU, reserved bits are set.\n"); + return 0; + } + } + + // continue unpacking remaining SN + ptr++; + header->sn |= (*ptr & 0xFF); // 8 bits SN + ptr++; + } else { + fprintf(stderr, "Unsupported SN length\n"); + return 0; + } + + // Read optional part + if (header->si == rlc_nr_si_field_t::last_segment || + header->si == rlc_nr_si_field_t::neither_first_nor_last_segment) { + // read SO + header->so = (*ptr & 0xFF) << 8; + ptr++; + header->so |= (*ptr & 0xFF); + ptr++; + } + + // return consumed bytes + return (ptr - payload); +} + +uint32_t rlc_um_nr_packed_length(const rlc_um_nr_pdu_header_t& header) +{ + uint32_t len = 0; + if (header.si == rlc_nr_si_field_t::full_sdu || header.si == rlc_nr_si_field_t::first_segment) { + len = 1; + if (header.sn_size == rlc_um_nr_sn_size_t::size12bits) { + len++; + } + } else { + if (header.sn_size == rlc_um_nr_sn_size_t::size6bits) { + len = 3; + } else { + len = 4; + } + } + return len; +} + +uint32_t rlc_um_nr_write_data_pdu_header(const rlc_um_nr_pdu_header_t& header, byte_buffer_t* pdu) +{ + // Make room for the header + uint32_t len = rlc_um_nr_packed_length(header); + pdu->msg -= len; + uint8_t* ptr = pdu->msg; + + // write SI field + *ptr = (header.si & 0x03) << 6; // 2 bits SI + + if (header.si == rlc_nr_si_field_t::full_sdu) { + // that's all .. + ptr++; + } else { + if (header.sn_size == rlc_um_nr_sn_size_t::size6bits) { + // write SN + *ptr |= (header.sn & 0x3f); // 6 bit SN + ptr++; + } else { + // 12bit SN + *ptr |= (header.sn & 0xf); // 4 bit SN + ptr++; + *ptr = (header.sn & 0xFF); // remaining 8 bit SN + ptr++; + } + if (header.so) { + // write SO + *ptr = (header.so) >> 8; // first part of SO + ptr++; + *ptr = (header.so & 0xFF); // second part of SO + ptr++; + } + } + + pdu->N_bytes += ptr - pdu->msg; + + return len; +} + +} // namespace srslte diff --git a/lib/test/upper/CMakeLists.txt b/lib/test/upper/CMakeLists.txt index 797b963a5..be0d6be74 100644 --- a/lib/test/upper/CMakeLists.txt +++ b/lib/test/upper/CMakeLists.txt @@ -35,6 +35,7 @@ target_link_libraries(rlc_stress_test srslte_upper srslte_phy srslte_common ${Bo add_test(rlc_am_stress_test rlc_stress_test --mode=AM --loglevel 1 --sdu_gen_delay 250) add_test(rlc_um_stress_test rlc_stress_test --mode=UM --loglevel 1) add_test(rlc_tm_stress_test rlc_stress_test --mode=TM --loglevel 1 --random_opp=false) +add_test(rlc_um_nr_stress_test rlc_stress_test --rat NR --mode=UM --loglevel 1) set_tests_properties(rlc_am_stress_test PROPERTIES TIMEOUT 3000) set_tests_properties(rlc_um_stress_test PROPERTIES TIMEOUT 3000) set_tests_properties(rlc_tm_stress_test PROPERTIES TIMEOUT 3000) diff --git a/lib/test/upper/rlc_am_test.cc b/lib/test/upper/rlc_am_test.cc index 0e9471ae7..192f16b0a 100644 --- a/lib/test/upper/rlc_am_test.cc +++ b/lib/test/upper/rlc_am_test.cc @@ -146,6 +146,9 @@ bool basic_test() rlc_am_lte rlc1(&log1, 1, &tester, &tester, &timers); rlc_am_lte rlc2(&log2, 1, &tester, &tester, &timers); + // before configuring entity + assert(0 == rlc1.get_buffer_state()); + if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; } @@ -181,10 +184,14 @@ bool basic_test() } // Check statistics - if (rlc1.get_num_tx_bytes() != rlc2.get_num_rx_bytes()) { + rlc_bearer_metrics_t rlc1_metrics = rlc1.get_metrics(); + rlc_bearer_metrics_t rlc2_metrics = rlc2.get_metrics(); + + if (rlc1_metrics.num_tx_bytes != rlc2_metrics.num_rx_bytes) { return -1; } - if (rlc2.get_num_tx_bytes() != rlc1.get_num_rx_bytes()) { + + if (rlc2_metrics.num_tx_bytes != rlc1_metrics.num_rx_bytes) { return -1; } @@ -245,11 +252,15 @@ bool concat_test() assert(*(tester.sdus[i]->msg) == i); } - // check statistics - if (rlc1.get_num_tx_bytes() != rlc2.get_num_rx_bytes()) { + // Check statistics + rlc_bearer_metrics_t rlc1_metrics = rlc1.get_metrics(); + rlc_bearer_metrics_t rlc2_metrics = rlc2.get_metrics(); + + if (rlc1_metrics.num_tx_bytes != rlc2_metrics.num_rx_bytes) { return -1; } - if (rlc2.get_num_tx_bytes() != rlc1.get_num_rx_bytes()) { + + if (rlc2_metrics.num_tx_bytes != rlc1_metrics.num_rx_bytes) { return -1; } @@ -340,10 +351,15 @@ bool segment_test(bool in_seq_rx) assert(tester.sdus[i]->msg[j] == j); } - if (rlc1.get_num_tx_bytes() != rlc2.get_num_rx_bytes()) { + // Check statistics + rlc_bearer_metrics_t rlc1_metrics = rlc1.get_metrics(); + rlc_bearer_metrics_t rlc2_metrics = rlc2.get_metrics(); + + if (rlc1_metrics.num_tx_bytes != rlc2_metrics.num_rx_bytes) { return -1; } - if (rlc2.get_num_tx_bytes() != rlc1.get_num_rx_bytes()) { + + if (rlc2_metrics.num_tx_bytes != rlc1_metrics.num_rx_bytes) { return -1; } diff --git a/lib/test/upper/rlc_stress_test.cc b/lib/test/upper/rlc_stress_test.cc index e65d66e49..70ace7f79 100644 --- a/lib/test/upper/rlc_stress_test.cc +++ b/lib/test/upper/rlc_stress_test.cc @@ -35,12 +35,44 @@ #define LOG_HEX_LIMIT (-1) +#define PCAP 1 +#define PCAP_CRNTI (0x1001) +#define PCAP_TTI (666) + +#if PCAP +#include "srslte/common/mac_nr_pcap.h" +#include "srslte/common/mac_nr_pdu.h" +static std::unique_ptr pcap_handle = nullptr; +#endif + +int write_pdu_to_pcap(const bool is_dl, const uint32_t lcid, const uint8_t* payload, const uint32_t len) +{ +#if PCAP + if (pcap_handle) { + srslte::byte_buffer_t tx_buffer; + srslte::mac_nr_sch_pdu tx_pdu; + tx_pdu.init_tx(&tx_buffer, len + 10); + tx_pdu.add_sdu(lcid, payload, len); + tx_pdu.pack(); + if (is_dl) { + pcap_handle->write_dl_crnti(tx_buffer.msg, tx_buffer.N_bytes, PCAP_CRNTI, true, PCAP_TTI); + } else { + pcap_handle->write_ul_crnti(tx_buffer.msg, tx_buffer.N_bytes, PCAP_CRNTI, true, PCAP_TTI); + } + + return SRSLTE_SUCCESS; + } +#endif + return SRSLTE_ERROR; +} + using namespace std; using namespace srsue; using namespace srslte; namespace bpo = boost::program_options; typedef struct { + std::string rat; std::string mode; uint32_t sdu_size; uint32_t test_duration_sec; @@ -72,6 +104,7 @@ void parse_args(stress_test_args_t *args, int argc, char *argv[]) { // Command line or config file options bpo::options_description common("Configuration options"); common.add_options() + ("rat", bpo::value(&args->rat)->default_value("LTE"), "The RLC version to use (LTE/NR)") ("mode", bpo::value(&args->mode)->default_value("AM"), "Whether to test RLC acknowledged or unacknowledged mode (AM/UM)") ("duration", bpo::value(&args->test_duration_sec)->default_value(5), "Duration (sec)") ("sdu_size", bpo::value(&args->sdu_size)->default_value(1500), "Size of SDUs") @@ -171,6 +204,8 @@ private: pdu_len = cut_pdu_len; } rx_rlc->write_pdu(lcid, pdu->msg, pdu_len); + write_pdu_to_pcap(is_dl, 4, pdu->msg, pdu_len); + if (is_dl) { pcap->write_dl_am_ccch(pdu->msg, pdu_len); } else { @@ -304,34 +339,56 @@ void stress_test(stress_test_args_t args) rlc_pcap pcap; uint32_t lcid = 1; - if (args.write_pcap) { - pcap.open("rlc_stress_test.pcap", 0); - } + rlc_config_t cnfg_ = {}; + if (args.rat == "LTE") { + if (args.mode == "AM") { + // config RLC AM bearer + cnfg_.rlc_mode = rlc_mode_t::am; + cnfg_.am.max_retx_thresh = 4; + cnfg_.am.poll_byte = 25 * 1000; + cnfg_.am.poll_pdu = 4; + cnfg_.am.t_poll_retx = 5; + cnfg_.am.t_reordering = 5; + cnfg_.am.t_status_prohibit = 5; + } else if (args.mode == "UM") { + // config UM bearer + cnfg_.rlc_mode = rlc_mode_t::um; + cnfg_.um.t_reordering = 5; + cnfg_.um.rx_mod = 32; + cnfg_.um.rx_sn_field_length = rlc_umd_sn_size_t::size5bits; + cnfg_.um.rx_window_size = 16; + cnfg_.um.tx_sn_field_length = rlc_umd_sn_size_t::size5bits; + cnfg_.um.tx_mod = 32; + } else if (args.mode == "TM") { + // use default LCID in TM + lcid = 0; + } else { + cout << "Unsupported RLC mode " << args.mode << ", exiting." << endl; + exit(-1); + } - rlc_config_t cnfg_; - if (args.mode == "AM") { - // config RLC AM bearer - cnfg_.rlc_mode = rlc_mode_t::am; - cnfg_.am.max_retx_thresh = 4; - cnfg_.am.poll_byte = 25*1000; - cnfg_.am.poll_pdu = 4; - cnfg_.am.t_poll_retx = 5; - cnfg_.am.t_reordering = 5; - cnfg_.am.t_status_prohibit = 5; - } else if (args.mode == "UM") { - // config UM bearer - cnfg_.rlc_mode = rlc_mode_t::um; - cnfg_.um.t_reordering = 5; - cnfg_.um.rx_mod = 32; - cnfg_.um.rx_sn_field_length = rlc_umd_sn_size_t::size5bits; - cnfg_.um.rx_window_size = 16; - cnfg_.um.tx_sn_field_length = rlc_umd_sn_size_t::size5bits; - cnfg_.um.tx_mod = 32; - } else if (args.mode == "TM") { - // use default LCID in TM - lcid = 0; +#if PCAP + if (args.write_pcap) { + pcap.open("rlc_stress_test.pcap", 0); + } +#endif + + } else if (args.rat == "NR") { + if (args.mode == "UM") { + cnfg_ = rlc_config_t::default_rlc_um_nr_config(6); + } else { + cout << "Unsupported RLC mode " << args.mode << ", exiting." << endl; + exit(-1); + } + +#if PCAP + if (args.write_pcap) { + pcap_handle = std::unique_ptr(new srslte::mac_nr_pcap()); + pcap_handle->open("rlc_stress_test_nr.pcap"); + } +#endif } else { - cout << "Unsupported RLC mode " << args.mode << ", exiting." << endl; + cout << "Unsupported RAT mode " << args.rat << ", exiting." << endl; exit(-1); } @@ -394,20 +451,20 @@ void stress_test(stress_test_args_t args) rlc_metrics_t metrics = {}; rlc1.get_metrics(metrics); - printf("RLC1 received %d SDUs in %ds (%.2f/s), Throughput: DL=%4.2f Mbps, UL=%4.2f Mbps\n", + printf("RLC1 received %d SDUs in %ds (%.2f/s), Tx=%" PRIu64 " B, Rx=%" PRIu64 " B\n", tester1.get_nof_rx_pdus(), args.test_duration_sec, - static_cast(tester1.get_nof_rx_pdus()/args.test_duration_sec), - metrics.dl_tput_mbps[lcid], - metrics.ul_tput_mbps[lcid]); + static_cast(tester1.get_nof_rx_pdus() / args.test_duration_sec), + metrics.bearer[lcid].num_tx_bytes, + metrics.bearer[lcid].num_rx_bytes); rlc2.get_metrics(metrics); - printf("RLC2 received %d SDUs in %ds (%.2f/s), Throughput: DL=%4.2f Mbps, UL=%4.2f Mbps\n", + printf("RLC2 received %d SDUs in %ds (%.2f/s), Tx=%" PRIu64 " B, Rx=%" PRIu64 " B\n", tester2.get_nof_rx_pdus(), args.test_duration_sec, - static_cast(tester2.get_nof_rx_pdus()/args.test_duration_sec), - metrics.dl_tput_mbps[lcid], - metrics.ul_tput_mbps[lcid]); + static_cast(tester2.get_nof_rx_pdus() / args.test_duration_sec), + metrics.bearer[lcid].num_tx_bytes, + metrics.bearer[lcid].num_rx_bytes); } diff --git a/lib/test/upper/rlc_test_common.h b/lib/test/upper/rlc_test_common.h index 92ae0e9b6..d3d4b8eea 100644 --- a/lib/test/upper/rlc_test_common.h +++ b/lib/test/upper/rlc_test_common.h @@ -35,10 +35,23 @@ public: // PDCP interface void write_pdu(uint32_t lcid, unique_byte_buffer_t sdu) { + // check length if (lcid != 3 && sdu->N_bytes != expected_sdu_len) { printf("Received PDU with size %d, expected %d. Exiting.\n", sdu->N_bytes, expected_sdu_len); exit(-1); } + + // check content + uint8_t first_byte = *sdu->msg; + for (uint32_t i = 0; i < sdu->N_bytes; i++) { + if (sdu->msg[i] != first_byte) { + printf("Received corrupted SDU with size %d. Exiting.\n", sdu->N_bytes); + srslte_vec_fprint_byte(stdout, sdu->msg, sdu->N_bytes); + exit(-1); + } + } + + // srslte_vec_fprint_byte(stdout, sdu->msg, sdu->N_bytes); sdus.push_back(std::move(sdu)); } void write_pdu_bcch_bch(unique_byte_buffer_t sdu) {} diff --git a/lib/test/upper/rlc_um_data_test.cc b/lib/test/upper/rlc_um_data_test.cc index f70dacefe..06d3cc641 100644 --- a/lib/test/upper/rlc_um_data_test.cc +++ b/lib/test/upper/rlc_um_data_test.cc @@ -19,9 +19,9 @@ * */ -#include -#include "srslte/upper/rlc_um.h" +#include "srslte/upper/rlc_um_lte.h" #include +#include // Fixed header only uint8_t pdu1[] = {0x18 ,0xE2}; diff --git a/lib/test/upper/rlc_um_nr_pdu_test.cc b/lib/test/upper/rlc_um_nr_pdu_test.cc index 730b1bb4c..1f359e7cf 100644 --- a/lib/test/upper/rlc_um_nr_pdu_test.cc +++ b/lib/test/upper/rlc_um_nr_pdu_test.cc @@ -21,7 +21,7 @@ #include "srslte/config.h" #include "srslte/upper/rlc.h" -#include "srslte/upper/rlc_um.h" +#include "srslte/upper/rlc_um_nr.h" #include #include @@ -53,7 +53,7 @@ int write_pdu_to_pcap(const uint32_t lcid, const uint8_t* payload, const uint32_ #if PCAP if (pcap_handle) { byte_buffer_t tx_buffer; - srslte::mac_nr_pdu tx_pdu; + srslte::mac_nr_sch_pdu tx_pdu; tx_pdu.init_tx(&tx_buffer, len + 10); tx_pdu.add_sdu(lcid, payload, len); tx_pdu.pack(); diff --git a/lib/test/upper/rlc_um_nr_test.cc b/lib/test/upper/rlc_um_nr_test.cc index 22ea2d5c3..81aae371b 100644 --- a/lib/test/upper/rlc_um_nr_test.cc +++ b/lib/test/upper/rlc_um_nr_test.cc @@ -23,7 +23,7 @@ #include "srslte/common/log_filter.h" #include "srslte/config.h" #include "srslte/upper/rlc.h" -#include "srslte/upper/rlc_um.h" +#include "srslte/upper/rlc_um_nr.h" #include #include @@ -54,8 +54,8 @@ int write_pdu_to_pcap(const uint32_t lcid, const uint8_t* payload, const uint32_ { #if PCAP if (pcap_handle) { - byte_buffer_t tx_buffer; - srslte::nr_mac_pdu tx_pdu; + byte_buffer_t tx_buffer; + srslte::mac_nr_sch_pdu tx_pdu; tx_pdu.init_tx(&tx_buffer, len + 10); tx_pdu.add_sdu(lcid, payload, len); tx_pdu.pack(); @@ -76,29 +76,47 @@ srslte::byte_buffer_t make_pdu_and_log(const std::array& tv) return pdu; } -// Basic test to write UM PDU with 6 bit SN +// Helper class to create two pre-configured RLC instances +class rlc_um_nr_test_context1 +{ +public: + rlc_um_nr_test_context1() : + log1("RLC_UM_1"), + log2("RLC_UM_2"), + timers(16), + rlc1(&log1, 3, &tester, &tester, &timers), + rlc2(&log2, 3, &tester, &tester, &timers) + { + // setup logging + log1.set_level(srslte::LOG_LEVEL_DEBUG); + log2.set_level(srslte::LOG_LEVEL_DEBUG); + log1.set_hex_limit(-1); + log2.set_hex_limit(-1); + + // configure RLC entities + rlc_config_t cnfg = rlc_config_t::default_rlc_um_nr_config(6); + if (rlc1.configure(cnfg) != true) { + fprintf(stderr, "Couldn't configure RLC1 object\n"); + } + if (rlc2.configure(cnfg) != true) { + fprintf(stderr, "Couldn't configure RLC2 object\n"); + } + + tester.set_expected_sdu_len(1); + } + + srslte::log_filter log1, log2; + srslte::timer_handler timers; + rlc_um_tester tester; + rlc_um_nr rlc1, rlc2; +}; + +// Basic test to write UM PDU with 6 bit SN (full SDUs are transmitted in each PDU) int rlc_um_nr_test1() { - srslte::log_filter log1("RLC_UM_1"); - srslte::log_filter log2("RLC_UM_2"); - log1.set_level(srslte::LOG_LEVEL_DEBUG); - log2.set_level(srslte::LOG_LEVEL_DEBUG); - log1.set_hex_limit(-1); - log2.set_hex_limit(-1); - rlc_um_tester tester; - srslte::timer_handler timers(16); - const uint32_t num_sdus = 5; - int len = 0; + rlc_um_nr_test_context1 ctxt; - rlc_um rlc1(&log1, 3, &tester, &tester, &timers); - rlc_um rlc2(&log2, 3, &tester, &tester, &timers); - - rlc_config_t cnfg = rlc_config_t::default_rlc_um_nr_config(6); - - TESTASSERT(rlc1.configure(cnfg) == true); - TESTASSERT(rlc2.configure(cnfg) == true); - - tester.set_expected_sdu_len(1); + const uint32_t num_sdus = 5, num_pdus = 5; // Push 5 SDUs into RLC1 byte_buffer_pool* pool = byte_buffer_pool::get_instance(); @@ -107,53 +125,49 @@ int rlc_um_nr_test1() sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); *sdu_bufs[i]->msg = i; // Write the index into the buffer sdu_bufs[i]->N_bytes = 1; // Give each buffer a size of 1 byte - rlc1.write_sdu(std::move(sdu_bufs[i])); + ctxt.rlc1.write_sdu(std::move(sdu_bufs[i])); } - TESTASSERT(14 == rlc1.get_buffer_state()); + TESTASSERT(14 == ctxt.rlc1.get_buffer_state()); // Read 5 PDUs from RLC1 (1 byte each) - unique_byte_buffer_t pdu_bufs[num_sdus]; - for (uint32_t i = 0; i < num_sdus; i++) { + unique_byte_buffer_t pdu_bufs[num_pdus]; + for (uint32_t i = 0; i < num_pdus; i++) { pdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); - len = rlc1.read_pdu(pdu_bufs[i]->msg, 4); // 3 bytes for header + payload + int len = ctxt.rlc1.read_pdu(pdu_bufs[i]->msg, 4); // 3 bytes for header + payload pdu_bufs[i]->N_bytes = len; // write PCAP write_pdu_to_pcap(4, pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); } - TESTASSERT(0 == rlc1.get_buffer_state()); + TESTASSERT(0 == ctxt.rlc1.get_buffer_state()); - // TODO: add receive test + // Write 5 PDUs into RLC2 + for (uint32_t i = 0; i < num_pdus; i++) { + ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); + } + + TESTASSERT(0 == ctxt.rlc2.get_buffer_state()); + + TESTASSERT(num_sdus == ctxt.tester.get_num_sdus()); + for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) { + TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == 1); + TESTASSERT(*(ctxt.tester.sdus[i]->msg) == i); + } return SRSLTE_SUCCESS; } // Basic test for SDU segmentation -int rlc_um_nr_test2() +int rlc_um_nr_test2(bool reverse_rx = false) { - srslte::log_filter log1("RLC_UM_1"); - srslte::log_filter log2("RLC_UM_2"); - log1.set_level(srslte::LOG_LEVEL_DEBUG); - log2.set_level(srslte::LOG_LEVEL_DEBUG); - log1.set_hex_limit(-1); - log2.set_hex_limit(-1); - rlc_um_tester tester; - srslte::timer_handler timers(16); - const uint32_t num_sdus = 1; - const uint32_t sdu_size = 100; - int len = 0; + rlc_um_nr_test_context1 ctxt; - rlc_um rlc1(&log1, 3, &tester, &tester, &timers); - rlc_um rlc2(&log2, 3, &tester, &tester, &timers); + const uint32_t num_sdus = 1; + const uint32_t sdu_size = 100; - rlc_config_t cnfg = rlc_config_t::default_rlc_um_nr_config(6); - - TESTASSERT(rlc1.configure(cnfg) == true); - TESTASSERT(rlc2.configure(cnfg) == true); - - tester.set_expected_sdu_len(1); + ctxt.tester.set_expected_sdu_len(sdu_size); // Push SDUs into RLC1 byte_buffer_pool* pool = byte_buffer_pool::get_instance(); @@ -165,27 +179,386 @@ int rlc_um_nr_test2() sdu_bufs[i]->msg[k] = i; } sdu_bufs[i]->N_bytes = sdu_size; - rlc1.write_sdu(std::move(sdu_bufs[i])); + ctxt.rlc1.write_sdu(std::move(sdu_bufs[i])); } // FIXME: check buffer state calculation - TESTASSERT(103 == rlc1.get_buffer_state()); + TESTASSERT(103 == ctxt.rlc1.get_buffer_state()); // Read PDUs from RLC1 with grant of 25 Bytes each const uint32_t max_num_pdus = 10; + uint32 num_pdus = 0; unique_byte_buffer_t pdu_bufs[max_num_pdus]; - for (uint32_t i = 0; i < max_num_pdus; i++) { - pdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); - len = rlc1.read_pdu(pdu_bufs[i]->msg, 25); // 3 bytes for header + payload - pdu_bufs[i]->N_bytes = len; + + while (ctxt.rlc1.get_buffer_state() != 0 && num_pdus < max_num_pdus) { + pdu_bufs[num_pdus] = srslte::allocate_unique_buffer(*pool, true); + int len = ctxt.rlc1.read_pdu(pdu_bufs[num_pdus]->msg, 25); // 3 bytes for header + payload + pdu_bufs[num_pdus]->N_bytes = len; // write PCAP - write_pdu_to_pcap(4, pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); + write_pdu_to_pcap(4, pdu_bufs[num_pdus]->msg, pdu_bufs[num_pdus]->N_bytes); + + num_pdus++; } - TESTASSERT(0 == rlc1.get_buffer_state()); + TESTASSERT(0 == ctxt.rlc1.get_buffer_state()); - // TODO: add receive test + // Write PDUs into RLC2 + if (reverse_rx) { + // receive PDUs in reverse order + for (uint32_t i = num_pdus; i > 0; i--) { + ctxt.rlc2.write_pdu(pdu_bufs[i - 1]->msg, pdu_bufs[i - 1]->N_bytes); + } + } else { + // receive PDUs in order + for (uint32_t i = 0; i < num_pdus; i++) { + ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); + } + } + + TESTASSERT(0 == ctxt.rlc2.get_buffer_state()); + + TESTASSERT(num_sdus == ctxt.tester.get_num_sdus()); + for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) { + TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == sdu_size); + TESTASSERT(*(ctxt.tester.sdus[i]->msg) == i); + } + + return SRSLTE_SUCCESS; +} + +// Test reception of segmented RLC PDUs (two different SDUs with same PDU segmentation) +int rlc_um_nr_test4() +{ + rlc_um_nr_test_context1 ctxt; + + const uint32_t num_sdus = 2; + const uint32_t sdu_size = 100; + + ctxt.tester.set_expected_sdu_len(sdu_size); + + // Push SDUs into RLC1 + byte_buffer_pool* pool = byte_buffer_pool::get_instance(); + unique_byte_buffer_t sdu_bufs[num_sdus]; + for (uint32_t i = 0; i < num_sdus; i++) { + sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); + // Write the index into the buffer + for (uint32_t k = 0; k < sdu_size; ++k) { + sdu_bufs[i]->msg[k] = i; + } + sdu_bufs[i]->N_bytes = sdu_size; + ctxt.rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + // FIXME: check buffer state calculation + int bsr = ctxt.rlc1.get_buffer_state(); + TESTASSERT(bsr == 205); + + // Read PDUs from RLC1 with grant of 25 Bytes each + const uint32_t max_num_pdus = 20; + uint32 num_pdus = 0; + unique_byte_buffer_t pdu_bufs[max_num_pdus]; + + while (ctxt.rlc1.get_buffer_state() != 0 && num_pdus < max_num_pdus) { + pdu_bufs[num_pdus] = srslte::allocate_unique_buffer(*pool, true); + int len = ctxt.rlc1.read_pdu(pdu_bufs[num_pdus]->msg, 25); // 3 bytes for header + payload + pdu_bufs[num_pdus]->N_bytes = len; + num_pdus++; + } + + TESTASSERT(0 == ctxt.rlc1.get_buffer_state()); + + // Write PDUs into RLC2 (except 1 and 6 + for (uint32_t i = 0; i < num_pdus; i++) { + if (i != 1 && i != 6) { + ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); + write_pdu_to_pcap(4, pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); + } + } + + // write remaining two PDUs in reverse-order (so SN=1 is received first) + ctxt.rlc2.write_pdu(pdu_bufs[6]->msg, pdu_bufs[6]->N_bytes); + write_pdu_to_pcap(4, pdu_bufs[6]->msg, pdu_bufs[6]->N_bytes); + + ctxt.rlc2.write_pdu(pdu_bufs[1]->msg, pdu_bufs[1]->N_bytes); + write_pdu_to_pcap(4, pdu_bufs[1]->msg, pdu_bufs[1]->N_bytes); + + TESTASSERT(0 == ctxt.rlc2.get_buffer_state()); + + TESTASSERT(num_sdus == ctxt.tester.get_num_sdus()); + for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) { + TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == sdu_size); + + // common tester makes sure the all bytes within the SDU are the same, but it doesn't verify the SDUs are the ones + // transmitted, so check this here + if (i == 0) { + // first SDU is SN=1 + TESTASSERT(*(ctxt.tester.sdus.at(i)->msg) == 0x01); + } else { + // second SDU is SN=0 + TESTASSERT(*(ctxt.tester.sdus.at(i)->msg) == 0x00); + } + } + + return SRSLTE_SUCCESS; +} + +// Handling of re-transmitted segments (e.g. after PHY retransmission) +int rlc_um_nr_test5(const uint32_t last_sn) +{ + rlc_um_nr_test_context1 ctxt; + + const uint32_t num_sdus = 1; + const uint32_t sdu_size = 100; + + ctxt.tester.set_expected_sdu_len(sdu_size); + + // Push SDUs into RLC1 + byte_buffer_pool* pool = byte_buffer_pool::get_instance(); + unique_byte_buffer_t sdu_bufs[num_sdus]; + for (uint32_t i = 0; i < num_sdus; i++) { + sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); + // Write the index into the buffer + for (uint32_t k = 0; k < sdu_size; ++k) { + sdu_bufs[i]->msg[k] = i; + } + sdu_bufs[i]->N_bytes = sdu_size; + ctxt.rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + // FIXME: check buffer state calculation + TESTASSERT(103 == ctxt.rlc1.get_buffer_state()); + + // Read PDUs from RLC1 with grant of 25 Bytes each + const uint32_t max_num_pdus = 10; + uint32 num_pdus = 0; + unique_byte_buffer_t pdu_bufs[max_num_pdus]; + + while (ctxt.rlc1.get_buffer_state() != 0 && num_pdus < max_num_pdus) { + pdu_bufs[num_pdus] = srslte::allocate_unique_buffer(*pool, true); + int len = ctxt.rlc1.read_pdu(pdu_bufs[num_pdus]->msg, 25); // 3 bytes for header + payload + pdu_bufs[num_pdus]->N_bytes = len; + + // write PCAP + write_pdu_to_pcap(4, pdu_bufs[num_pdus]->msg, pdu_bufs[num_pdus]->N_bytes); + + num_pdus++; + } + + TESTASSERT(0 == ctxt.rlc1.get_buffer_state()); + + // Write alls PDUs twice into RLC2 (except third) + for (uint32_t k = 0; k < 2; k++) { + for (uint32_t i = 0; i < num_pdus; i++) { + if (i != last_sn) { + ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); + } + } + } + + // Write third PDU + ctxt.rlc2.write_pdu(pdu_bufs[last_sn]->msg, pdu_bufs[last_sn]->N_bytes); + + TESTASSERT(0 == ctxt.rlc2.get_buffer_state()); + + TESTASSERT(num_sdus == ctxt.tester.get_num_sdus()); + for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) { + TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == sdu_size); + TESTASSERT(*(ctxt.tester.sdus[i]->msg) == i); + } + + return SRSLTE_SUCCESS; +} + +// Test of wrap-around of reassembly window +int rlc_um_nr_test6() +{ + rlc_um_nr_test_context1 ctxt; + + const uint32_t num_sdus = 64; + const uint32_t sdu_size = 10; + + ctxt.tester.set_expected_sdu_len(sdu_size); + + // Push SDUs into RLC1 + byte_buffer_pool* pool = byte_buffer_pool::get_instance(); + unique_byte_buffer_t sdu_bufs[num_sdus]; + for (uint32_t i = 0; i < num_sdus; i++) { + sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); + // Write the index into the buffer + for (uint32_t k = 0; k < sdu_size; ++k) { + sdu_bufs[i]->msg[k] = i; + } + sdu_bufs[i]->N_bytes = sdu_size; + ctxt.rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + // FIXME: check buffer state calculation + // TESTASSERT(103 == ctxt.rlc1.get_buffer_state()); + + // Read PDUs from RLC1 with grant of 8 Bytes each + const uint32_t max_num_pdus = num_sdus * 2; // we need 2 PDUs for each SDU + uint32 num_pdus = 0; + unique_byte_buffer_t pdu_bufs[max_num_pdus]; + + while (ctxt.rlc1.get_buffer_state() != 0 && num_pdus < max_num_pdus) { + pdu_bufs[num_pdus] = srslte::allocate_unique_buffer(*pool, true); + int len = ctxt.rlc1.read_pdu(pdu_bufs[num_pdus]->msg, 8); // 3 bytes for header + payload + pdu_bufs[num_pdus]->N_bytes = len; + + // write PCAP + write_pdu_to_pcap(4, pdu_bufs[num_pdus]->msg, pdu_bufs[num_pdus]->N_bytes); + + num_pdus++; + } + + TESTASSERT(0 == ctxt.rlc1.get_buffer_state()); + + // Write PDUs into RLC2 + for (uint32_t i = 0; i < num_pdus; i++) { + ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); + } + + TESTASSERT(0 == ctxt.rlc2.get_buffer_state()); + + TESTASSERT(num_sdus == ctxt.tester.get_num_sdus()); + for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) { + TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == sdu_size); + TESTASSERT(*(ctxt.tester.sdus[i]->msg) == i); + } + + return SRSLTE_SUCCESS; +} + +// Segment loss received too many new PDUs (lost PDU outside of reassembly window) +int rlc_um_nr_test7() +{ + rlc_um_nr_test_context1 ctxt; + + const uint32_t num_sdus = 64; + const uint32_t sdu_size = 10; + + ctxt.tester.set_expected_sdu_len(sdu_size); + + // Push SDUs into RLC1 + byte_buffer_pool* pool = byte_buffer_pool::get_instance(); + unique_byte_buffer_t sdu_bufs[num_sdus]; + for (uint32_t i = 0; i < num_sdus; i++) { + sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); + // Write the index into the buffer + for (uint32_t k = 0; k < sdu_size; ++k) { + sdu_bufs[i]->msg[k] = i; + } + sdu_bufs[i]->N_bytes = sdu_size; + ctxt.rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + // FIXME: check buffer state calculation + // TESTASSERT(103 == ctxt.rlc1.get_buffer_state()); + + // Read PDUs from RLC1 with grant of 8 Bytes each + const uint32_t max_num_pdus = num_sdus * 2; // we need 2 PDUs for each SDU + uint32 num_pdus = 0; + unique_byte_buffer_t pdu_bufs[max_num_pdus]; + + while (ctxt.rlc1.get_buffer_state() != 0 && num_pdus < max_num_pdus) { + pdu_bufs[num_pdus] = srslte::allocate_unique_buffer(*pool, true); + int len = ctxt.rlc1.read_pdu(pdu_bufs[num_pdus]->msg, 8); // 3 bytes for header + payload + pdu_bufs[num_pdus]->N_bytes = len; + + // write PCAP + write_pdu_to_pcap(4, pdu_bufs[num_pdus]->msg, pdu_bufs[num_pdus]->N_bytes); + + num_pdus++; + } + + TESTASSERT(0 == ctxt.rlc1.get_buffer_state()); + + // Write PDUs into RLC2 (except 11th) + for (uint32_t i = 0; i < num_pdus; i++) { + if (i != 10) { + ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); + } + } + + TESTASSERT(0 == ctxt.rlc2.get_buffer_state()); + + TESTASSERT(num_sdus - 1 == ctxt.tester.get_num_sdus()); + for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) { + TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == sdu_size); + } + + rlc_bearer_metrics_t rlc2_metrics = ctxt.rlc2.get_metrics(); + TESTASSERT(rlc2_metrics.num_lost_pdus == 1); + + return SRSLTE_SUCCESS; +} + +// Segment loss and expiry of reassembly timer +int rlc_um_nr_test8() +{ + rlc_um_nr_test_context1 ctxt; + + const uint32_t num_sdus = 10; + const uint32_t sdu_size = 10; + + ctxt.tester.set_expected_sdu_len(sdu_size); + + // Push SDUs into RLC1 + byte_buffer_pool* pool = byte_buffer_pool::get_instance(); + unique_byte_buffer_t sdu_bufs[num_sdus]; + for (uint32_t i = 0; i < num_sdus; i++) { + sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); + // Write the index into the buffer + for (uint32_t k = 0; k < sdu_size; ++k) { + sdu_bufs[i]->msg[k] = i; + } + sdu_bufs[i]->N_bytes = sdu_size; + ctxt.rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + // FIXME: check buffer state calculation + // TESTASSERT(103 == ctxt.rlc1.get_buffer_state()); + + // Read PDUs from RLC1 with grant of 8 Bytes each + const uint32_t max_num_pdus = 20 * 2; // we need 2 PDUs for each SDU + uint32 num_pdus = 0; + unique_byte_buffer_t pdu_bufs[max_num_pdus]; + + while (ctxt.rlc1.get_buffer_state() != 0 && num_pdus < max_num_pdus) { + pdu_bufs[num_pdus] = srslte::allocate_unique_buffer(*pool, true); + int len = ctxt.rlc1.read_pdu(pdu_bufs[num_pdus]->msg, 8); // 3 bytes for header + payload + pdu_bufs[num_pdus]->N_bytes = len; + + // write PCAP + write_pdu_to_pcap(4, pdu_bufs[num_pdus]->msg, pdu_bufs[num_pdus]->N_bytes); + + num_pdus++; + } + + TESTASSERT(0 == ctxt.rlc1.get_buffer_state()); + + // Write PDUs into RLC2 (except 2nd) + for (uint32_t i = 0; i < num_pdus; i++) { + if (i != 2) { + ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); + } + } + + TESTASSERT(0 == ctxt.rlc2.get_buffer_state()); + + // let t-reassembly expire + while (ctxt.timers.nof_running_timers() != 0) { + ctxt.timers.step_all(); + } + + TESTASSERT(num_sdus - 1 == ctxt.tester.get_num_sdus()); + for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) { + TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == sdu_size); + } + + rlc_bearer_metrics_t rlc2_metrics = ctxt.rlc2.get_metrics(); + TESTASSERT(rlc2_metrics.num_lost_pdus == 1); return SRSLTE_SUCCESS; } @@ -206,5 +579,39 @@ int main(int argc, char** argv) fprintf(stderr, "rlc_um_nr_test2() failed.\n"); return SRSLTE_ERROR; } + + // same like above but PDUs delivered in reverse order + if (rlc_um_nr_test2(true)) { + fprintf(stderr, "rlc_um_nr_test2(true) failed.\n"); + return SRSLTE_ERROR; + } + + if (rlc_um_nr_test4()) { + fprintf(stderr, "rlc_um_nr_test4() failed.\n"); + return SRSLTE_ERROR; + } + + for (uint32_t i = 0; i < 5; ++i) { + if (rlc_um_nr_test5(i)) { + fprintf(stderr, "rlc_um_nr_test5() for i=%d failed.\n", i); + return SRSLTE_ERROR; + } + } + + if (rlc_um_nr_test6()) { + fprintf(stderr, "rlc_um_nr_test6() failed.\n"); + return SRSLTE_ERROR; + } + + if (rlc_um_nr_test7()) { + fprintf(stderr, "rlc_um_nr_test7() failed.\n"); + return SRSLTE_ERROR; + } + + if (rlc_um_nr_test8()) { + fprintf(stderr, "rlc_um_nr_test8() failed.\n"); + return SRSLTE_ERROR; + } + return SRSLTE_SUCCESS; } diff --git a/lib/test/upper/rlc_um_test.cc b/lib/test/upper/rlc_um_test.cc index 87401fe83..e439b864f 100644 --- a/lib/test/upper/rlc_um_test.cc +++ b/lib/test/upper/rlc_um_test.cc @@ -21,7 +21,7 @@ #include "rlc_test_common.h" #include "srslte/common/log_filter.h" -#include "srslte/upper/rlc_um.h" +#include "srslte/upper/rlc_um_lte.h" #include #define TESTASSERT(cond) \ @@ -39,34 +39,44 @@ using namespace srslte; using namespace srsue; using namespace asn1::rrc; +// Helper class to create two pre-configured RLC instances +class rlc_um_lte_test_context1 +{ +public: + rlc_um_lte_test_context1() : + log1("RLC_UM_1"), + log2("RLC_UM_2"), + timers(16), + rlc1(&log1, 3, &tester, &tester, &timers), + rlc2(&log2, 3, &tester, &tester, &timers) + { + // setup logging + log1.set_level(srslte::LOG_LEVEL_DEBUG); + log2.set_level(srslte::LOG_LEVEL_DEBUG); + log1.set_hex_limit(-1); + log2.set_hex_limit(-1); + + // configure RLC entities + rlc_config_t cnfg = rlc_config_t::default_rlc_um_config(10); + if (rlc1.configure(cnfg) != true) { + fprintf(stderr, "Couldn't configure RLC1 object\n"); + } + if (rlc2.configure(cnfg) != true) { + fprintf(stderr, "Couldn't configure RLC2 object\n"); + } + + tester.set_expected_sdu_len(1); + } + + srslte::log_filter log1, log2; + srslte::timer_handler timers; + rlc_um_tester tester; + rlc_um_lte rlc1, rlc2; +}; + int basic_test() { - srslte::log_filter log1("RLC_UM_1"); - srslte::log_filter log2("RLC_UM_2"); - log1.set_level(srslte::LOG_LEVEL_DEBUG); - log2.set_level(srslte::LOG_LEVEL_DEBUG); - log1.set_hex_limit(-1); - log2.set_hex_limit(-1); - rlc_um_tester tester; - srslte::timer_handler timers(16); - int len = 0; - - rlc_um rlc1(&log1, 3, &tester, &tester, &timers); - rlc_um rlc2(&log2, 3, &tester, &tester, &timers); - - rlc_config_t cnfg = rlc_config_t::default_rlc_um_config(10); - cnfg.rlc_mode = rlc_mode_t::um; - cnfg.um.t_reordering = 5; - cnfg.um.rx_sn_field_length = rlc_umd_sn_size_t::size10bits; - cnfg.um.rx_window_size = 512; - cnfg.um.rx_mod = 1024; - cnfg.um.tx_sn_field_length = rlc_umd_sn_size_t::size10bits; - cnfg.um.tx_mod = 1024; - - TESTASSERT(rlc1.configure(cnfg) == true); - TESTASSERT(rlc2.configure(cnfg) == true); - - tester.set_expected_sdu_len(1); + rlc_um_lte_test_context1 ctxt; // Push 5 SDUs into RLC1 byte_buffer_pool* pool = byte_buffer_pool::get_instance(); @@ -76,33 +86,31 @@ int basic_test() sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); *sdu_bufs[i]->msg = i; // Write the index into the buffer sdu_bufs[i]->N_bytes = 1; // Give each buffer a size of 1 byte - rlc1.write_sdu(std::move(sdu_bufs[i])); + ctxt.rlc1.write_sdu(std::move(sdu_bufs[i])); } - TESTASSERT(14 == rlc1.get_buffer_state()); + TESTASSERT(14 == ctxt.rlc1.get_buffer_state()); // Read 5 PDUs from RLC1 (1 byte each) byte_buffer_t pdu_bufs[NBUFS]; - for(int i=0;iN_bytes == 1); - TESTASSERT(*(tester.sdus[i]->msg) == i); + TESTASSERT(NBUFS == ctxt.tester.get_num_sdus()); + for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) { + TESTASSERT(ctxt.tester.sdus.at(i)->N_bytes == 1); + TESTASSERT(*(ctxt.tester.sdus[i]->msg) == i); } return 0; @@ -110,87 +118,53 @@ int basic_test() int loss_test() { - srslte::log_filter log1("RLC_UM_1"); - srslte::log_filter log2("RLC_UM_2"); - log1.set_level(srslte::LOG_LEVEL_DEBUG); - log2.set_level(srslte::LOG_LEVEL_DEBUG); - log1.set_hex_limit(-1); - log2.set_hex_limit(-1); - rlc_um_tester tester; - srslte::timer_handler timers(16); - int len = 0; - - rlc_um rlc1(&log1, 3, &tester, &tester, &timers); - rlc_um rlc2(&log2, 3, &tester, &tester, &timers); - - rlc_config_t cnfg = rlc_config_t::default_rlc_um_config(10); - - rlc1.configure(cnfg); - rlc2.configure(cnfg); - - tester.set_expected_sdu_len(1); + rlc_um_lte_test_context1 ctxt; // Push 5 SDUs into RLC1 byte_buffer_pool* pool = byte_buffer_pool::get_instance(); unique_byte_buffer_t sdu_bufs[NBUFS]; - for(int i=0;imsg[0] = i; // Write the index into the buffer sdu_bufs[i]->N_bytes = 1; // Give each buffer a size of 1 byte - rlc1.write_sdu(std::move(sdu_bufs[i])); + ctxt.rlc1.write_sdu(std::move(sdu_bufs[i])); } - TESTASSERT(14 == rlc1.get_buffer_state()); + TESTASSERT(14 == ctxt.rlc1.get_buffer_state()); // Read 5 PDUs from RLC1 (1 byte each) byte_buffer_t pdu_bufs[NBUFS]; - for(int i=0;iis_expired()) - // timers.get(1)->step(); - while (timers.nof_running_timers() != 0) { - timers.step_all(); + while (ctxt.timers.nof_running_timers() != 0) { + ctxt.timers.step_all(); } - TESTASSERT(NBUFS - 1 == tester.sdus.size()); + TESTASSERT(NBUFS - 1 == ctxt.tester.sdus.size()); return 0; } int basic_mbsfn_test() { - srslte::log_filter log1("RLC_UM_1"); - srslte::log_filter log2("RLC_UM_2"); - log1.set_level(srslte::LOG_LEVEL_DEBUG); - log2.set_level(srslte::LOG_LEVEL_DEBUG); - log1.set_hex_limit(-1); - log2.set_hex_limit(-1); - rlc_um_tester tester; - srslte::timer_handler timers(16); - int len = 0; + rlc_um_lte_test_context1 ctxt; - rlc_um rlc1(&log1, 3, &tester, &tester, &timers); - rlc_um rlc2(&log2, 3, &tester, &tester, &timers); - - rlc1.configure(rlc_config_t::mch_config()); - rlc2.configure(rlc_config_t::mch_config()); - - tester.set_expected_sdu_len(1); + // configure as MCH + ctxt.rlc1.configure(rlc_config_t::mch_config()); + ctxt.rlc2.configure(rlc_config_t::mch_config()); // Push 5 SDUs into RLC1 byte_buffer_pool* pool = byte_buffer_pool::get_instance(); @@ -200,33 +174,32 @@ int basic_mbsfn_test() sdu_bufs[i] = srslte::allocate_unique_buffer(*pool, true); sdu_bufs[i]->msg[0] = i; // Write the index into the buffer sdu_bufs[i]->N_bytes = 1; // Give each buffer a size of 1 byte - rlc1.write_sdu(std::move(sdu_bufs[i])); + ctxt.rlc1.write_sdu(std::move(sdu_bufs[i])); } - TESTASSERT(13 == rlc1.get_buffer_state()); + TESTASSERT(13 == ctxt.rlc1.get_buffer_state()); // Read 5 PDUs from RLC1 (1 byte each) byte_buffer_t pdu_bufs[NBUFS*2]; for(int i=0;iN_bytes == 1); - TESTASSERT(*(tester.sdus[i]->msg) == i); + TESTASSERT(NBUFS == ctxt.tester.sdus.size()); + for (uint32_t i = 0; i < ctxt.tester.sdus.size(); i++) { + TESTASSERT(ctxt.tester.sdus[i]->N_bytes == 1); + TESTASSERT(*(ctxt.tester.sdus[i]->msg) == i); } return 0; @@ -245,30 +218,18 @@ int basic_mbsfn_test() // PDUs than rx_mod are received. int reassmble_test() { - srslte::log_filter log1("RLC_UM_1"); - srslte::log_filter log2("RLC_UM_2"); - log1.set_level(srslte::LOG_LEVEL_DEBUG); - log2.set_level(srslte::LOG_LEVEL_DEBUG); - log1.set_hex_limit(-1); - log2.set_hex_limit(-1); - rlc_um_tester tester; - srslte::timer_handler timers(16); - int len = 0; - - rlc_um rlc1(&log1, 3, &tester, &tester, &timers); - rlc_um rlc2(&log2, 3, &tester, &tester, &timers); + rlc_um_lte_test_context1 ctxt; + // reconfigure them with 5bit SNs rlc_config_t cnfg = rlc_config_t::default_rlc_um_config(5); - - rlc1.configure(cnfg); - rlc2.configure(cnfg); + ctxt.rlc1.configure(cnfg); + ctxt.rlc2.configure(cnfg); // Push SDUs into RLC1 const int n_sdus = 25; const int sdu_len = 100; - tester.set_expected_sdu_len(sdu_len); - + ctxt.tester.set_expected_sdu_len(sdu_len); const int n_sdu_first_batch = 17; @@ -280,7 +241,7 @@ int reassmble_test() sdu_bufs[i]->msg[k] = i; } sdu_bufs[i]->N_bytes = sdu_len; // Give each buffer a size of 1 byte - rlc1.write_sdu(std::move(sdu_bufs[i])); + ctxt.rlc1.write_sdu(std::move(sdu_bufs[i])); } // Read PDUs from RLC1 (use smaller grant for first PDU and large for the rest) @@ -290,7 +251,7 @@ int reassmble_test() for(int i=0;iallocate(); - len = rlc1.read_pdu(pdu_bufs[i]->msg, (i == 0) ? sdu_len * 3 / 4 : sdu_len * 1.25); + int len = ctxt.rlc1.read_pdu(pdu_bufs[i]->msg, (i == 0) ? sdu_len * 3 / 4 : sdu_len * 1.25); pdu_bufs[i]->N_bytes = len; if (len) { n_pdus++; @@ -300,7 +261,7 @@ int reassmble_test() } printf("Generated %d PDUs in first batch\n", n_pdus); - TESTASSERT(0 == rlc1.get_buffer_state()); + TESTASSERT(0 == ctxt.rlc1.get_buffer_state()); // push second batch of SDUs for (int i = n_sdu_first_batch; i < n_sdus; ++i) { @@ -309,14 +270,13 @@ int reassmble_test() sdu_bufs[i]->msg[k] = i; } sdu_bufs[i]->N_bytes = sdu_len; // Give each buffer a size of 1 byte - rlc1.write_sdu(std::move(sdu_bufs[i])); + ctxt.rlc1.write_sdu(std::move(sdu_bufs[i])); } // Read second batch of PDUs (use large grants) - for(int i=n_pdus;iallocate(); - len = rlc1.read_pdu(pdu_bufs[i]->msg, sdu_len * 1.25); + int len = ctxt.rlc1.read_pdu(pdu_bufs[i]->msg, sdu_len * 1.25); pdu_bufs[i]->N_bytes = len; if (len) { n_pdus++; @@ -332,14 +292,14 @@ int reassmble_test() for(int i=0;imsg, pdu_bufs[i]->N_bytes); + ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); } } // We should have received one SDU less than we tx'ed - TESTASSERT(tester.sdus.size() == n_sdus - 1); - for (uint32_t i = 0; i < tester.sdus.size(); ++i) { - TESTASSERT(tester.sdus[i]->N_bytes == sdu_len); + TESTASSERT(ctxt.tester.sdus.size() == n_sdus - 1); + for (uint32_t i = 0; i < ctxt.tester.sdus.size(); ++i) { + TESTASSERT(ctxt.tester.sdus[i]->N_bytes == sdu_len); } return 0; @@ -354,29 +314,18 @@ int reassmble_test() // Therefore, one SDU less should be received than was tx'ed. int reassmble_test2() { - srslte::log_filter log1("RLC_UM_1"); - srslte::log_filter log2("RLC_UM_2"); - log1.set_level(srslte::LOG_LEVEL_DEBUG); - log2.set_level(srslte::LOG_LEVEL_DEBUG); - log1.set_hex_limit(-1); - log2.set_hex_limit(-1); - rlc_um_tester tester; - srslte::timer_handler timers(16); - int len = 0; - - rlc_um rlc1(&log1, 3, &tester, &tester, &timers); - rlc_um rlc2(&log2, 3, &tester, &tester, &timers); + rlc_um_lte_test_context1 ctxt; + // reconfigure them with 5bit SNs rlc_config_t cnfg = rlc_config_t::default_rlc_um_config(5); - - rlc1.configure(cnfg); - rlc2.configure(cnfg); + ctxt.rlc1.configure(cnfg); + ctxt.rlc2.configure(cnfg); // Push SDUs into RLC1 const int n_sdus = 25; const int sdu_len = 100; - tester.set_expected_sdu_len(sdu_len); + ctxt.tester.set_expected_sdu_len(sdu_len); const int n_sdu_first_batch = 17; byte_buffer_pool* pool = byte_buffer_pool::get_instance(); @@ -387,16 +336,15 @@ int reassmble_test2() sdu_bufs[i]->msg[k] = i; } sdu_bufs[i]->N_bytes = sdu_len; - rlc1.write_sdu(std::move(sdu_bufs[i])); + ctxt.rlc1.write_sdu(std::move(sdu_bufs[i])); } const int max_n_pdus = 100; int n_pdus = 0; byte_buffer_t* pdu_bufs[max_n_pdus]; - for(int i=0;iallocate(); - len = rlc1.read_pdu(pdu_bufs[i]->msg, (i == 0) ? sdu_len * .75 : sdu_len * .25); + int len = ctxt.rlc1.read_pdu(pdu_bufs[i]->msg, (i == 0) ? sdu_len * .75 : sdu_len * .25); pdu_bufs[i]->N_bytes = len; if (len) { n_pdus++; @@ -406,7 +354,7 @@ int reassmble_test2() } printf("Generated %d PDUs in first batch\n", n_pdus); - TESTASSERT(0 == rlc1.get_buffer_state()); + TESTASSERT(0 == ctxt.rlc1.get_buffer_state()); // push second batch of SDUs for (int i = n_sdu_first_batch; i < n_sdus; ++i) { @@ -415,14 +363,14 @@ int reassmble_test2() sdu_bufs[i]->msg[k] = i; } sdu_bufs[i]->N_bytes = sdu_len; // Give each buffer a size of 1 byte - rlc1.write_sdu(std::move(sdu_bufs[i])); + ctxt.rlc1.write_sdu(std::move(sdu_bufs[i])); } // Read second batch of PDUs for(int i=n_pdus;iallocate(); - len = rlc1.read_pdu(pdu_bufs[i]->msg, sdu_len * 1.25); + int len = ctxt.rlc1.read_pdu(pdu_bufs[i]->msg, sdu_len * 1.25); pdu_bufs[i]->N_bytes = len; if (len) { n_pdus++; @@ -436,14 +384,14 @@ int reassmble_test2() // Write all PDUs into RLC2 except first one for(int i=0;imsg, pdu_bufs[i]->N_bytes); + ctxt.rlc2.write_pdu(pdu_bufs[i]->msg, pdu_bufs[i]->N_bytes); } } // We should have received one SDU less than we tx'ed - TESTASSERT(tester.sdus.size() == n_sdus - 1); - for (uint32_t i = 0; i < tester.sdus.size(); ++i) { - TESTASSERT(tester.sdus[i]->N_bytes == sdu_len); + TESTASSERT(ctxt.tester.sdus.size() == n_sdus - 1); + for (uint32_t i = 0; i < ctxt.tester.sdus.size(); ++i) { + TESTASSERT(ctxt.tester.sdus[i]->N_bytes == sdu_len); } return 0;